mirror of
				https://github.com/lingble/twenty.git
				synced 2025-10-31 04:37:56 +00:00 
			
		
		
		
	Unselect record table records on table body click (#8306)
We have previously fixed the unselection of table records on click outside. However, the ref was mispositioned as it selected the full height table. In the case of low record numbers, we also want the unselection to happen on table body click
This commit is contained in:
		| @@ -3,14 +3,20 @@ import { isNonEmptyString, isNull } from '@sniptt/guards'; | |||||||
|  |  | ||||||
| import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; | import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; | ||||||
| import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider'; | import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider'; | ||||||
|  | import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId'; | ||||||
| import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState'; | import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState'; | ||||||
|  | import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; | ||||||
| import { RecordTableBody } from '@/object-record/record-table/record-table-body/components/RecordTableBody'; | import { RecordTableBody } from '@/object-record/record-table/record-table-body/components/RecordTableBody'; | ||||||
| import { RecordTableBodyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyEffect'; | import { RecordTableBodyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyEffect'; | ||||||
|  | import { RecordTableBodyUnselectEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyUnselectEffect'; | ||||||
| import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader'; | import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader'; | ||||||
| import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState'; | import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState'; | ||||||
| import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState'; | import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState'; | ||||||
| import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState'; | import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState'; | ||||||
|  | import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; | ||||||
|  | import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; | ||||||
| import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; | import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; | ||||||
|  | import { useRef } from 'react'; | ||||||
|  |  | ||||||
| const StyledTable = styled.table` | const StyledTable = styled.table` | ||||||
|   border-radius: ${({ theme }) => theme.border.radius.sm}; |   border-radius: ${({ theme }) => theme.border.radius.sm}; | ||||||
| @@ -32,11 +38,17 @@ export const RecordTable = ({ | |||||||
|   objectNameSingular, |   objectNameSingular, | ||||||
|   onColumnsChange, |   onColumnsChange, | ||||||
| }: RecordTableProps) => { | }: RecordTableProps) => { | ||||||
|  |   const tableBodyRef = useRef<HTMLTableElement>(null); | ||||||
|  |  | ||||||
|   const isRecordTableInitialLoading = useRecoilComponentValueV2( |   const isRecordTableInitialLoading = useRecoilComponentValueV2( | ||||||
|     isRecordTableInitialLoadingComponentState, |     isRecordTableInitialLoadingComponentState, | ||||||
|     recordTableId, |     recordTableId, | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|  |   const { toggleClickOutsideListener } = useClickOutsideListener( | ||||||
|  |     RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID, | ||||||
|  |   ); | ||||||
|  |  | ||||||
|   const tableRowIds = useRecoilComponentValueV2( |   const tableRowIds = useRecoilComponentValueV2( | ||||||
|     tableRowIdsComponentState, |     tableRowIdsComponentState, | ||||||
|     recordTableId, |     recordTableId, | ||||||
| @@ -47,6 +59,10 @@ export const RecordTable = ({ | |||||||
|     recordTableId, |     recordTableId, | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|  |   const { resetTableRowSelection, setRowSelected } = useRecordTable({ | ||||||
|  |     recordTableId, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|   const recordTableIsEmpty = |   const recordTableIsEmpty = | ||||||
|     !isRecordTableInitialLoading && |     !isRecordTableInitialLoading && | ||||||
|     tableRowIds.length === 0 && |     tableRowIds.length === 0 && | ||||||
| @@ -67,15 +83,32 @@ export const RecordTable = ({ | |||||||
|         viewBarId={viewBarId} |         viewBarId={viewBarId} | ||||||
|       > |       > | ||||||
|         <RecordTableBodyEffect /> |         <RecordTableBodyEffect /> | ||||||
|  |         <RecordTableBodyUnselectEffect | ||||||
|  |           tableBodyRef={tableBodyRef} | ||||||
|  |           recordTableId={recordTableId} | ||||||
|  |         /> | ||||||
|         {recordTableIsEmpty ? ( |         {recordTableIsEmpty ? ( | ||||||
|           <RecordTableEmptyState /> |           <RecordTableEmptyState /> | ||||||
|         ) : ( |         ) : ( | ||||||
|           <StyledTable className="entity-table-cell"> |           <> | ||||||
|  |             <StyledTable className="entity-table-cell" ref={tableBodyRef}> | ||||||
|               <RecordTableHeader |               <RecordTableHeader | ||||||
|                 objectMetadataNameSingular={objectNameSingular} |                 objectMetadataNameSingular={objectNameSingular} | ||||||
|               /> |               /> | ||||||
|               <RecordTableBody /> |               <RecordTableBody /> | ||||||
|             </StyledTable> |             </StyledTable> | ||||||
|  |             <DragSelect | ||||||
|  |               dragSelectable={tableBodyRef} | ||||||
|  |               onDragSelectionStart={() => { | ||||||
|  |                 resetTableRowSelection(); | ||||||
|  |                 toggleClickOutsideListener(false); | ||||||
|  |               }} | ||||||
|  |               onDragSelectionChange={setRowSelected} | ||||||
|  |               onDragSelectionEnd={() => { | ||||||
|  |                 toggleClickOutsideListener(true); | ||||||
|  |               }} | ||||||
|  |             /> | ||||||
|  |           </> | ||||||
|         )} |         )} | ||||||
|       </RecordTableContextProvider> |       </RecordTableContextProvider> | ||||||
|     </RecordTableComponentInstance> |     </RecordTableComponentInstance> | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import styled from '@emotion/styled'; | import styled from '@emotion/styled'; | ||||||
| import { useRef } from 'react'; |  | ||||||
| import { useRecoilCallback } from 'recoil'; | import { useRecoilCallback } from 'recoil'; | ||||||
|  |  | ||||||
| import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; | import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; | ||||||
| @@ -7,15 +6,11 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata' | |||||||
| import { RecordTable } from '@/object-record/record-table/components/RecordTable'; | import { RecordTable } from '@/object-record/record-table/components/RecordTable'; | ||||||
| import { EntityDeleteContext } from '@/object-record/record-table/contexts/EntityDeleteHookContext'; | import { EntityDeleteContext } from '@/object-record/record-table/contexts/EntityDeleteHookContext'; | ||||||
| import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; | import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; | ||||||
| import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; |  | ||||||
| import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; | import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; | ||||||
| import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields'; | import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields'; | ||||||
| import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField'; | import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField'; | ||||||
|  |  | ||||||
| import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext'; | import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext'; | ||||||
| import { useRecordTable } from '../hooks/useRecordTable'; |  | ||||||
|  |  | ||||||
| import { RecordTableInternalEffect } from './RecordTableInternalEffect'; |  | ||||||
|  |  | ||||||
| const StyledTableWithHeader = styled.div` | const StyledTableWithHeader = styled.div` | ||||||
|   height: 100%; |   height: 100%; | ||||||
| @@ -45,12 +40,6 @@ export const RecordTableWithWrappers = ({ | |||||||
|   recordTableId, |   recordTableId, | ||||||
|   viewBarId, |   viewBarId, | ||||||
| }: RecordTableWithWrappersProps) => { | }: RecordTableWithWrappersProps) => { | ||||||
|   const tableBodyRef = useRef<HTMLDivElement>(null); |  | ||||||
|  |  | ||||||
|   const { resetTableRowSelection, setRowSelected } = useRecordTable({ |  | ||||||
|     recordTableId, |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   const { saveViewFields } = useSaveCurrentViewFields(viewBarId); |   const { saveViewFields } = useSaveCurrentViewFields(viewBarId); | ||||||
|  |  | ||||||
|   const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular }); |   const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular }); | ||||||
| @@ -72,25 +61,14 @@ export const RecordTableWithWrappers = ({ | |||||||
|         <RecordUpdateContext.Provider value={updateRecordMutation}> |         <RecordUpdateContext.Provider value={updateRecordMutation}> | ||||||
|           <StyledTableWithHeader> |           <StyledTableWithHeader> | ||||||
|             <StyledTableContainer> |             <StyledTableContainer> | ||||||
|               <StyledTableInternalContainer ref={tableBodyRef}> |               <StyledTableInternalContainer> | ||||||
|                 <RecordTable |                 <RecordTable | ||||||
|                   viewBarId={viewBarId} |                   viewBarId={viewBarId} | ||||||
|                   recordTableId={recordTableId} |                   recordTableId={recordTableId} | ||||||
|                   objectNameSingular={objectNameSingular} |                   objectNameSingular={objectNameSingular} | ||||||
|                   onColumnsChange={handleColumnsChange} |                   onColumnsChange={handleColumnsChange} | ||||||
|                 /> |                 /> | ||||||
|                 <DragSelect |  | ||||||
|                   dragSelectable={tableBodyRef} |  | ||||||
|                   onDragSelectionStart={() => { |  | ||||||
|                     resetTableRowSelection(); |  | ||||||
|                   }} |  | ||||||
|                   onDragSelectionChange={setRowSelected} |  | ||||||
|                 /> |  | ||||||
|               </StyledTableInternalContainer> |               </StyledTableInternalContainer> | ||||||
|               <RecordTableInternalEffect |  | ||||||
|                 tableBodyRef={tableBodyRef} |  | ||||||
|                 recordTableId={recordTableId} |  | ||||||
|               /> |  | ||||||
|             </StyledTableContainer> |             </StyledTableContainer> | ||||||
|           </StyledTableWithHeader> |           </StyledTableWithHeader> | ||||||
|         </RecordUpdateContext.Provider> |         </RecordUpdateContext.Provider> | ||||||
|   | |||||||
| @@ -7,16 +7,16 @@ import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkey | |||||||
| import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; | import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; | ||||||
| import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2'; | import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2'; | ||||||
| 
 | 
 | ||||||
| type RecordTableInternalEffectProps = { | type RecordTableBodyUnselectEffectProps = { | ||||||
|   recordTableId: string; |  | ||||||
|   tableBodyRef: React.RefObject<HTMLDivElement>; |   tableBodyRef: React.RefObject<HTMLDivElement>; | ||||||
|  |   recordTableId: string; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const RecordTableInternalEffect = ({ | export const RecordTableBodyUnselectEffect = ({ | ||||||
|   recordTableId, |  | ||||||
|   tableBodyRef, |   tableBodyRef, | ||||||
| }: RecordTableInternalEffectProps) => { |   recordTableId, | ||||||
|   const leaveTableFocus = useLeaveTableFocus(recordTableId); | }: RecordTableBodyUnselectEffectProps) => { | ||||||
|  |   const leaveTableFocus = useLeaveTableFocus(); | ||||||
| 
 | 
 | ||||||
|   const { resetTableRowSelection, useMapKeyboardToSoftFocus } = useRecordTable({ |   const { resetTableRowSelection, useMapKeyboardToSoftFocus } = useRecordTable({ | ||||||
|     recordTableId, |     recordTableId, | ||||||
| @@ -1,9 +1,9 @@ | |||||||
| import { RefObject } from 'react'; |  | ||||||
| import { | import { | ||||||
|   boxesIntersect, |   boxesIntersect, | ||||||
|   useSelectionContainer, |   useSelectionContainer, | ||||||
| } from '@air/react-drag-to-select'; | } from '@air/react-drag-to-select'; | ||||||
| import { useTheme } from '@emotion/react'; | import { useTheme } from '@emotion/react'; | ||||||
|  | import { RefObject } from 'react'; | ||||||
| import { RGBA } from 'twenty-ui'; | import { RGBA } from 'twenty-ui'; | ||||||
|  |  | ||||||
| import { useDragSelect } from '../hooks/useDragSelect'; | import { useDragSelect } from '../hooks/useDragSelect'; | ||||||
| @@ -11,13 +11,15 @@ import { useDragSelect } from '../hooks/useDragSelect'; | |||||||
| type DragSelectProps = { | type DragSelectProps = { | ||||||
|   dragSelectable: RefObject<HTMLElement>; |   dragSelectable: RefObject<HTMLElement>; | ||||||
|   onDragSelectionChange: (id: string, selected: boolean) => void; |   onDragSelectionChange: (id: string, selected: boolean) => void; | ||||||
|   onDragSelectionStart?: () => void; |   onDragSelectionStart?: (event: MouseEvent) => void; | ||||||
|  |   onDragSelectionEnd?: (event: MouseEvent) => void; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const DragSelect = ({ | export const DragSelect = ({ | ||||||
|   dragSelectable, |   dragSelectable, | ||||||
|   onDragSelectionChange, |   onDragSelectionChange, | ||||||
|   onDragSelectionStart, |   onDragSelectionStart, | ||||||
|  |   onDragSelectionEnd, | ||||||
| }: DragSelectProps) => { | }: DragSelectProps) => { | ||||||
|   const theme = useTheme(); |   const theme = useTheme(); | ||||||
|   const { isDragSelectionStartEnabled } = useDragSelect(); |   const { isDragSelectionStartEnabled } = useDragSelect(); | ||||||
| @@ -37,6 +39,7 @@ export const DragSelect = ({ | |||||||
|       return true; |       return true; | ||||||
|     }, |     }, | ||||||
|     onSelectionStart: onDragSelectionStart, |     onSelectionStart: onDragSelectionStart, | ||||||
|  |     onSelectionEnd: onDragSelectionEnd, | ||||||
|     onSelectionChange: (box) => { |     onSelectionChange: (box) => { | ||||||
|       const scrollAwareBox = { |       const scrollAwareBox = { | ||||||
|         ...box, |         ...box, | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { clickOutsideListenerCallbacksComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerCallbacksComponentState'; | import { clickOutsideListenerCallbacksComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerCallbacksComponentState'; | ||||||
| import { clickOutsideListenerIsActivatedComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerIsActivatedComponentState'; | import { clickOutsideListenerIsActivatedComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerIsActivatedComponentState'; | ||||||
| import { clickOutsideListenerIsMouseDownInsideComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerIsMouseDownInsideComponentState'; | import { clickOutsideListenerIsMouseDownInsideComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerIsMouseDownInsideComponentState'; | ||||||
| import { lockedListenerIdState } from '@/ui/utilities/pointer-event/states/lockedListenerIdState'; | import { clickOutsideListenerMouseDownHappenedComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerMouseDownHappenedComponentState'; | ||||||
| import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; | import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; | ||||||
| import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; | import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; | ||||||
|  |  | ||||||
| @@ -22,6 +22,9 @@ export const useClickOustideListenerStates = (componentId: string) => { | |||||||
|       clickOutsideListenerIsActivatedComponentState, |       clickOutsideListenerIsActivatedComponentState, | ||||||
|       scopeId, |       scopeId, | ||||||
|     ), |     ), | ||||||
|     lockedListenerIdState, |     getClickOutsideListenerMouseDownHappenedState: extractComponentState( | ||||||
|  |       clickOutsideListenerMouseDownHappenedComponentState, | ||||||
|  |       scopeId, | ||||||
|  |     ), | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -7,17 +7,14 @@ import { | |||||||
|   useListenClickOutsideV2, |   useListenClickOutsideV2, | ||||||
| } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2'; | } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2'; | ||||||
| import { ClickOutsideListenerCallback } from '@/ui/utilities/pointer-event/types/ClickOutsideListenerCallback'; | import { ClickOutsideListenerCallback } from '@/ui/utilities/pointer-event/types/ClickOutsideListenerCallback'; | ||||||
| import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; |  | ||||||
| import { toSpliced } from '~/utils/array/toSpliced'; | import { toSpliced } from '~/utils/array/toSpliced'; | ||||||
| import { isDefined } from '~/utils/isDefined'; | import { isDefined } from '~/utils/isDefined'; | ||||||
|  |  | ||||||
| export const useClickOutsideListener = (componentId: string) => { | export const useClickOutsideListener = (componentId: string) => { | ||||||
|   // TODO: improve typing |  | ||||||
|   const scopeId = getScopeIdFromComponentId(componentId) ?? ''; |  | ||||||
|  |  | ||||||
|   const { |   const { | ||||||
|     getClickOutsideListenerIsActivatedState, |     getClickOutsideListenerIsActivatedState, | ||||||
|     getClickOutsideListenerCallbacksState, |     getClickOutsideListenerCallbacksState, | ||||||
|  |     getClickOutsideListenerMouseDownHappenedState, | ||||||
|   } = useClickOustideListenerStates(componentId); |   } = useClickOustideListenerStates(componentId); | ||||||
|  |  | ||||||
|   const useListenClickOutside = <T extends Element>({ |   const useListenClickOutside = <T extends Element>({ | ||||||
| @@ -53,8 +50,15 @@ export const useClickOutsideListener = (componentId: string) => { | |||||||
|     ({ set }) => |     ({ set }) => | ||||||
|       (activated: boolean) => { |       (activated: boolean) => { | ||||||
|         set(getClickOutsideListenerIsActivatedState, activated); |         set(getClickOutsideListenerIsActivatedState, activated); | ||||||
|  |  | ||||||
|  |         if (!activated) { | ||||||
|  |           set(getClickOutsideListenerMouseDownHappenedState, false); | ||||||
|  |         } | ||||||
|       }, |       }, | ||||||
|     [getClickOutsideListenerIsActivatedState], |     [ | ||||||
|  |       getClickOutsideListenerIsActivatedState, | ||||||
|  |       getClickOutsideListenerMouseDownHappenedState, | ||||||
|  |     ], | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   const registerOnClickOutsideCallback = useRecoilCallback( |   const registerOnClickOutsideCallback = useRecoilCallback( | ||||||
| @@ -148,7 +152,6 @@ export const useClickOutsideListener = (componentId: string) => { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   return { |   return { | ||||||
|     scopeId, |  | ||||||
|     useListenClickOutside, |     useListenClickOutside, | ||||||
|     toggleClickOutsideListener, |     toggleClickOutsideListener, | ||||||
|     useRegisterClickOutsideListenerCallback, |     useRegisterClickOutsideListenerCallback, | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ export const useListenClickOutsideV2 = <T extends Element>({ | |||||||
|   const { |   const { | ||||||
|     getClickOutsideListenerIsMouseDownInsideState, |     getClickOutsideListenerIsMouseDownInsideState, | ||||||
|     getClickOutsideListenerIsActivatedState, |     getClickOutsideListenerIsActivatedState, | ||||||
|  |     getClickOutsideListenerMouseDownHappenedState, | ||||||
|   } = useClickOustideListenerStates(listenerId); |   } = useClickOustideListenerStates(listenerId); | ||||||
|  |  | ||||||
|   const handleMouseDown = useRecoilCallback( |   const handleMouseDown = useRecoilCallback( | ||||||
| @@ -37,6 +38,8 @@ export const useListenClickOutsideV2 = <T extends Element>({ | |||||||
|           .getLoadable(getClickOutsideListenerIsActivatedState) |           .getLoadable(getClickOutsideListenerIsActivatedState) | ||||||
|           .getValue(); |           .getValue(); | ||||||
|  |  | ||||||
|  |         set(getClickOutsideListenerMouseDownHappenedState, true); | ||||||
|  |  | ||||||
|         const isListening = clickOutsideListenerIsActivated && enabled; |         const isListening = clickOutsideListenerIsActivated && enabled; | ||||||
|  |  | ||||||
|         if (!isListening) { |         if (!isListening) { | ||||||
| @@ -92,21 +95,32 @@ export const useListenClickOutsideV2 = <T extends Element>({ | |||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|     [ |     [ | ||||||
|  |       getClickOutsideListenerIsActivatedState, | ||||||
|  |       enabled, | ||||||
|       mode, |       mode, | ||||||
|       refs, |       refs, | ||||||
|       getClickOutsideListenerIsMouseDownInsideState, |       getClickOutsideListenerIsMouseDownInsideState, | ||||||
|       enabled, |       getClickOutsideListenerMouseDownHappenedState, | ||||||
|       getClickOutsideListenerIsActivatedState, |  | ||||||
|     ], |     ], | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   const handleClickOutside = useRecoilCallback( |   const handleClickOutside = useRecoilCallback( | ||||||
|     ({ snapshot }) => |     ({ snapshot }) => | ||||||
|       (event: MouseEvent | TouchEvent) => { |       (event: MouseEvent | TouchEvent) => { | ||||||
|  |         const clickOutsideListenerIsActivated = snapshot | ||||||
|  |           .getLoadable(getClickOutsideListenerIsActivatedState) | ||||||
|  |           .getValue(); | ||||||
|  |  | ||||||
|  |         const isListening = clickOutsideListenerIsActivated && enabled; | ||||||
|  |  | ||||||
|         const isMouseDownInside = snapshot |         const isMouseDownInside = snapshot | ||||||
|           .getLoadable(getClickOutsideListenerIsMouseDownInsideState) |           .getLoadable(getClickOutsideListenerIsMouseDownInsideState) | ||||||
|           .getValue(); |           .getValue(); | ||||||
|  |  | ||||||
|  |         const hasMouseDownHappened = snapshot | ||||||
|  |           .getLoadable(getClickOutsideListenerMouseDownHappenedState) | ||||||
|  |           .getValue(); | ||||||
|  |  | ||||||
|         if (mode === ClickOutsideMode.compareHTMLRef) { |         if (mode === ClickOutsideMode.compareHTMLRef) { | ||||||
|           const clickedElement = event.target as HTMLElement; |           const clickedElement = event.target as HTMLElement; | ||||||
|           let isClickedOnExcluded = false; |           let isClickedOnExcluded = false; | ||||||
| @@ -132,6 +146,8 @@ export const useListenClickOutsideV2 = <T extends Element>({ | |||||||
|             .some((ref) => ref.current?.contains(event.target as Node)); |             .some((ref) => ref.current?.contains(event.target as Node)); | ||||||
|  |  | ||||||
|           if ( |           if ( | ||||||
|  |             isListening && | ||||||
|  |             hasMouseDownHappened && | ||||||
|             !clickedOnAtLeastOneRef && |             !clickedOnAtLeastOneRef && | ||||||
|             !isMouseDownInside && |             !isMouseDownInside && | ||||||
|             !isClickedOnExcluded |             !isClickedOnExcluded | ||||||
| @@ -171,13 +187,21 @@ export const useListenClickOutsideV2 = <T extends Element>({ | |||||||
|               return true; |               return true; | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|           if (!clickedOnAtLeastOneRef && !isMouseDownInside) { |           if ( | ||||||
|  |             !clickedOnAtLeastOneRef && | ||||||
|  |             !isMouseDownInside && | ||||||
|  |             isListening && | ||||||
|  |             hasMouseDownHappened | ||||||
|  |           ) { | ||||||
|             callback(event); |             callback(event); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|     [ |     [ | ||||||
|  |       getClickOutsideListenerIsActivatedState, | ||||||
|  |       enabled, | ||||||
|       getClickOutsideListenerIsMouseDownInsideState, |       getClickOutsideListenerIsMouseDownInsideState, | ||||||
|  |       getClickOutsideListenerMouseDownHappenedState, | ||||||
|       mode, |       mode, | ||||||
|       refs, |       refs, | ||||||
|       excludeClassNames, |       excludeClassNames, | ||||||
|   | |||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; | ||||||
|  |  | ||||||
|  | export const clickOutsideListenerMouseDownHappenedComponentState = | ||||||
|  |   createComponentState<boolean>({ | ||||||
|  |     key: 'clickOutsideListenerMouseDownHappenedComponentState', | ||||||
|  |     defaultValue: false, | ||||||
|  |   }); | ||||||
| @@ -1,6 +0,0 @@ | |||||||
| import { createState } from 'twenty-ui'; |  | ||||||
|  |  | ||||||
| export const lockedListenerIdState = createState<string | null>({ |  | ||||||
|   key: 'lockedListenerIdState', |  | ||||||
|   defaultValue: null, |  | ||||||
| }); |  | ||||||
		Reference in New Issue
	
	Block a user
	 Charles Bochet
					Charles Bochet