diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/DateTimeFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/DateTimeFieldInput.stories.tsx index c21553579..010523e8f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/DateTimeFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/DateTimeFieldInput.stories.tsx @@ -6,12 +6,12 @@ import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope import { FieldMetadataType } from '~/generated/graphql'; import { FieldContextProvider } from '@/object-record/record-field/meta-types/components/FieldContextProvider'; +import { StorybookFieldInputDropdownFocusIdSetterEffect } from '~/testing/components/StorybookFieldInputDropdownFocusIdSetterEffect'; import { useDateTimeField } from '../../../hooks/useDateTimeField'; import { DateTimeFieldInput, DateTimeFieldInputProps, } from '../DateTimeFieldInput'; - const formattedDate = new Date(2022, 0, 1, 2, 0, 0); const DateFieldValueSetterEffect = ({ value }: { value: Date }) => { @@ -81,6 +81,7 @@ const DateFieldInputWithContext = ({ }} recordId={recordId} > + + + ({ @@ -25,9 +30,32 @@ export const useRegisterInputEvents = ({ onClickOutside?: (event: MouseEvent | TouchEvent, inputValue: T) => void; hotkeyScope: string; }) => { + const activeDropdownFocusId = useRecoilValue(activeDropdownFocusIdState); + + const { recordId, fieldDefinition } = useContext(FieldContext); + useListenClickOutside({ refs: [inputRef, copyRef].filter(isDefined), callback: (event) => { + const fieldDropdownFocusIdTableCell = getDropdownFocusIdForRecordField( + recordId, + fieldDefinition.fieldMetadataId, + 'table-cell', + ); + + const fieldDropdownFocusIdInlineCell = getDropdownFocusIdForRecordField( + recordId, + fieldDefinition.fieldMetadataId, + 'inline-cell', + ); + + if ( + activeDropdownFocusId !== fieldDropdownFocusIdTableCell && + activeDropdownFocusId !== fieldDropdownFocusIdInlineCell + ) { + return; + } + onClickOutside?.(event, inputValue); }, enabled: isDefined(onClickOutside), 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 a57d6ef92..60c45d49e 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 @@ -4,6 +4,8 @@ import { useRecoilCallback } from 'recoil'; import { internalHotkeysEnabledScopesState } from '@/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState'; import { useClickOustideListenerStates } from '@/ui/utilities/pointer-event/hooks/useClickOustideListenerStates'; +const CLICK_OUTSIDE_DEBUG_MODE = false; + export enum ClickOutsideMode { comparePixels = 'comparePixels', compareHTMLRef = 'compareHTMLRef', @@ -60,52 +62,60 @@ export const useListenClickOutside = ({ return; } - if (mode === ClickOutsideMode.compareHTMLRef) { - const clickedOnAtLeastOneRef = refs - .filter((ref) => !!ref.current) - .some((ref) => ref.current?.contains(event.target as Node)); + switch (mode) { + case ClickOutsideMode.compareHTMLRef: { + const clickedOnAtLeastOneRef = refs + .filter((ref) => !!ref.current) + .some((ref) => ref.current?.contains(event.target as Node)); - set( - getClickOutsideListenerIsMouseDownInsideState, - clickedOnAtLeastOneRef, - ); - } + set( + getClickOutsideListenerIsMouseDownInsideState, + clickedOnAtLeastOneRef, + ); + break; + } - if (mode === ClickOutsideMode.comparePixels) { - const clickedOnAtLeastOneRef = refs - .filter((ref) => !!ref.current) - .some((ref) => { - if (!ref.current) { - return false; - } + case ClickOutsideMode.comparePixels: { + const clickedOnAtLeastOneRef = refs + .filter((ref) => !!ref.current) + .some((ref) => { + if (!ref.current) { + return false; + } - const { x, y, width, height } = - ref.current.getBoundingClientRect(); + const { x, y, width, height } = + ref.current.getBoundingClientRect(); - const clientX = - 'clientX' in event - ? event.clientX - : event.changedTouches[0].clientX; - const clientY = - 'clientY' in event - ? event.clientY - : event.changedTouches[0].clientY; + const clientX = + 'clientX' in event + ? event.clientX + : event.changedTouches[0].clientX; + const clientY = + 'clientY' in event + ? event.clientY + : event.changedTouches[0].clientY; - if ( - clientX < x || - clientX > x + width || - clientY < y || - clientY > y + height - ) { - return false; - } - return true; - }); + if ( + clientX < x || + clientX > x + width || + clientY < y || + clientY > y + height + ) { + return false; + } + return true; + }); - set( - getClickOutsideListenerIsMouseDownInsideState, - clickedOnAtLeastOneRef, - ); + set( + getClickOutsideListenerIsMouseDownInsideState, + clickedOnAtLeastOneRef, + ); + break; + } + + default: { + break; + } } }, [ @@ -171,13 +181,30 @@ export const useListenClickOutside = ({ .filter((ref) => !!ref.current) .some((ref) => ref.current?.contains(event.target as Node)); - if ( + const shouldTrigger = isListening && hasMouseDownHappened && !clickedOnAtLeastOneRef && !isMouseDownInside && - !isClickedOnExcluded - ) { + !isClickedOnExcluded; + + if (CLICK_OUTSIDE_DEBUG_MODE) { + // eslint-disable-next-line no-console + console.log('click outside compare refs', { + listenerId, + shouldTrigger, + isListening, + hasMouseDownHappened, + clickedOnAtLeastOneRef, + isMouseDownInside, + isClickedOnExcluded, + hotkeyScope, + enabled, + event, + }); + } + + if (shouldTrigger) { callback(event); } } @@ -213,12 +240,28 @@ export const useListenClickOutside = ({ return true; }); - if ( + const shouldTrigger = !clickedOnAtLeastOneRef && !isMouseDownInside && isListening && - hasMouseDownHappened - ) { + hasMouseDownHappened; + + if (CLICK_OUTSIDE_DEBUG_MODE) { + // eslint-disable-next-line no-console + console.log('click outside compare pixel', { + listenerId, + shouldTrigger, + clickedOnAtLeastOneRef, + isMouseDownInside, + isListening, + hasMouseDownHappened, + hotkeyScope, + enabled, + event, + }); + } + + if (shouldTrigger) { callback(event); } } @@ -233,6 +276,7 @@ export const useListenClickOutside = ({ refs, excludeClassNames, callback, + listenerId, ], ); diff --git a/packages/twenty-front/src/testing/components/StorybookFieldInputDropdownFocusIdSetterEffect.tsx b/packages/twenty-front/src/testing/components/StorybookFieldInputDropdownFocusIdSetterEffect.tsx new file mode 100644 index 000000000..380d99370 --- /dev/null +++ b/packages/twenty-front/src/testing/components/StorybookFieldInputDropdownFocusIdSetterEffect.tsx @@ -0,0 +1,26 @@ +import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; +import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField'; +import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; +import { useContext, useEffect } from 'react'; + +export const StorybookFieldInputDropdownFocusIdSetterEffect = () => { + const { recordId, fieldDefinition } = useContext(FieldContext); + + const { setActiveDropdownFocusIdAndMemorizePrevious } = + useSetActiveDropdownFocusIdAndMemorizePrevious(); + + const fieldDropdownFocusIdTableCell = getDropdownFocusIdForRecordField( + recordId, + fieldDefinition.fieldMetadataId, + 'table-cell', + ); + + useEffect(() => { + setActiveDropdownFocusIdAndMemorizePrevious(fieldDropdownFocusIdTableCell); + }, [ + setActiveDropdownFocusIdAndMemorizePrevious, + fieldDropdownFocusIdTableCell, + ]); + + return null; +};