diff --git a/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx index b97388bb5..522f27689 100644 --- a/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx +++ b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx @@ -24,7 +24,7 @@ import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlur import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard'; import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard'; import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; -import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; +import { useResetFocusStackToRecordIndex } from '@/object-record/record-index/hooks/useResetFocusStackToRecordIndex'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow'; import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; @@ -40,6 +40,7 @@ import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffect import { useInitializeQueryParamState } from '~/modules/app/hooks/useInitializeQueryParamState'; import { isMatchingLocation } from '~/utils/isMatchingLocation'; import { getPageTitleFromPath } from '~/utils/title-utils'; + // TODO: break down into smaller functions and / or hooks // - moved usePageChangeEffectNavigateLocation into dedicated hook export const PageChangeEffect = () => { @@ -91,6 +92,8 @@ export const PageChangeEffect = () => { const { closeCommandMenu } = useCommandMenu(); + const { resetFocusStackToRecordIndex } = useResetFocusStackToRecordIndex(); + useEffect(() => { closeCommandMenu(); }, [location.pathname, closeCommandMenu]); @@ -130,25 +133,10 @@ export const PageChangeEffect = () => { unfocusBoardCard(); } } - }, [ - previousLocation, - resetTableSelections, - unfocusRecordTableRow, - deactivateRecordTableRow, - contextStoreCurrentViewType, - resetRecordSelection, - deactivateBoardCard, - unfocusBoardCard, - ]); - useEffect(() => { switch (true) { case isMatchingLocation(location, AppPath.RecordIndexPage): { - setHotkeyScope(RecordIndexHotkeyScope.RecordIndex, { - goto: true, - keyboardShortcutMenu: true, - searchRecords: true, - }); + resetFocusStackToRecordIndex(); break; } case isMatchingLocation(location, AppPath.RecordShowPage): { @@ -198,7 +186,19 @@ export const PageChangeEffect = () => { break; } } - }, [location, setHotkeyScope]); + }, [ + location, + setHotkeyScope, + previousLocation, + contextStoreCurrentViewType, + resetTableSelections, + unfocusRecordTableRow, + deactivateRecordTableRow, + resetRecordSelection, + deactivateBoardCard, + unfocusBoardCard, + resetFocusStackToRecordIndex, + ]); useEffect(() => { setTimeout(() => { diff --git a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenuCloseAnimationCompleteCleanup.test.tsx b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenuCloseAnimationCompleteCleanup.test.tsx index d8a5993f6..1752d5540 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenuCloseAnimationCompleteCleanup.test.tsx +++ b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenuCloseAnimationCompleteCleanup.test.tsx @@ -23,7 +23,7 @@ import { IconList } from 'twenty-ui/display'; const mockCloseDropdown = jest.fn(); const mockResetContextStoreStates = jest.fn(); const mockResetSelectedItem = jest.fn(); -const mockEmitRightDrawerCloseEvent = jest.fn(); +const mockEmitSidePanelCloseEvent = jest.fn(); jest.mock('@/ui/layout/dropdown/hooks/useDropdownV2', () => ({ useDropdownV2: () => ({ @@ -43,9 +43,9 @@ jest.mock('@/ui/layout/selectable-list/hooks/useSelectableList', () => ({ }), })); -jest.mock('@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent', () => ({ - emitRightDrawerCloseEvent: () => { - mockEmitRightDrawerCloseEvent(); +jest.mock('@/ui/layout/right-drawer/utils/emitSidePanelCloseEvent', () => ({ + emitSidePanelCloseEvent: () => { + mockEmitSidePanelCloseEvent(); }, })); @@ -224,7 +224,7 @@ describe('useCommandMenuCloseAnimationCompleteCleanup', () => { expect(mockCloseDropdown).toHaveBeenCalledTimes(1); expect(mockResetContextStoreStates).toHaveBeenCalledTimes(2); expect(mockResetSelectedItem).toHaveBeenCalledTimes(1); - expect(mockEmitRightDrawerCloseEvent).toHaveBeenCalledTimes(1); + expect(mockEmitSidePanelCloseEvent).toHaveBeenCalledTimes(1); expect(mockCloseDropdown).toHaveBeenCalledWith( COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID, diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts index 1091e12a5..9c0641b25 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts @@ -2,14 +2,14 @@ import { useRecoilCallback } from 'recoil'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; -import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId'; import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId'; import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu'; import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown'; +import { emitSidePanelOpenEvent } from '@/ui/layout/right-drawer/utils/emitSidePanelOpenEvent'; import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState'; -import { useRemoveFocusItemFromFocusStack } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStack'; +import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; import { useCallback } from 'react'; import { IconDotsVertical } from 'twenty-ui/display'; import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState'; @@ -18,7 +18,8 @@ export const useCommandMenu = () => { const { navigateCommandMenu } = useNavigateCommandMenu(); const { closeAnyOpenDropdown } = useCloseAnyOpenDropdown(); - const { removeFocusItemFromFocusStack } = useRemoveFocusItemFromFocusStack(); + const { removeFocusItemFromFocusStackById } = + useRemoveFocusItemFromFocusStackById(); const closeCommandMenu = useRecoilCallback( ({ set, snapshot }) => @@ -32,16 +33,16 @@ export const useCommandMenu = () => { set(isCommandMenuClosingState, true); set(isDragSelectionStartEnabledState, true); closeAnyOpenDropdown(); - removeFocusItemFromFocusStack({ + removeFocusItemFromFocusStackById({ focusId: SIDE_PANEL_FOCUS_ID, - memoizeKey: COMMAND_MENU_COMPONENT_INSTANCE_ID, }); } }, - [closeAnyOpenDropdown, removeFocusItemFromFocusStack], + [closeAnyOpenDropdown, removeFocusItemFromFocusStackById], ); const openCommandMenu = useCallback(() => { + emitSidePanelOpenEvent(); closeAnyOpenDropdown(); navigateCommandMenu({ page: CommandMenuPages.Root, diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts index 0d05e6e10..3d244a49c 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts @@ -14,7 +14,7 @@ import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpe import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; -import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent'; +import { emitSidePanelCloseEvent } from '@/ui/layout/right-drawer/utils/emitSidePanelCloseEvent'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; @@ -52,7 +52,7 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => { resetSelectedItem(); set(hasUserSelectedCommandState, false); - emitRightDrawerCloseEvent(); + emitSidePanelCloseEvent(); set(isCommandMenuClosingState, false); set( activeTabIdComponentState.atomFamily({ diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardDeactivateBoardCardEffect.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardDeactivateBoardCardEffect.tsx index 52fb10987..d42769b8b 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardDeactivateBoardCardEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardDeactivateBoardCardEffect.tsx @@ -1,13 +1,13 @@ import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard'; -import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose'; +import { useListenToSidePanelClosing } from '@/ui/layout/right-drawer/hooks/useListenToSidePanelClosing'; import { useContext } from 'react'; export const RecordBoardDeactivateBoardCardEffect = () => { const { recordBoardId } = useContext(RecordBoardContext); const { deactivateBoardCard } = useActiveRecordBoardCard(recordBoardId); - useListenRightDrawerClose(() => { + useListenToSidePanelClosing(() => { deactivateBoardCard(); }); diff --git a/packages/twenty-front/src/modules/object-record/record-index/constants/RecordIndexFocusId.ts b/packages/twenty-front/src/modules/object-record/record-index/constants/RecordIndexFocusId.ts new file mode 100644 index 000000000..711788489 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-index/constants/RecordIndexFocusId.ts @@ -0,0 +1 @@ +export const RECORD_INDEX_FOCUS_ID = 'record-index'; diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useResetFocusStackToRecordIndex.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useResetFocusStackToRecordIndex.ts new file mode 100644 index 000000000..9fad1fb41 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useResetFocusStackToRecordIndex.ts @@ -0,0 +1,37 @@ +import { RECORD_INDEX_FOCUS_ID } from '@/object-record/record-index/constants/RecordIndexFocusId'; +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; +import { useResetFocusStackToFocusItem } from '@/ui/utilities/focus/hooks/useResetFocusStackToFocusItem'; +import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; + +export const useResetFocusStackToRecordIndex = () => { + const { resetFocusStackToFocusItem } = useResetFocusStackToFocusItem(); + + const resetFocusStackToRecordIndex = () => { + resetFocusStackToFocusItem({ + focusStackItem: { + focusId: RECORD_INDEX_FOCUS_ID, + componentInstance: { + componentType: FocusComponentType.PAGE, + componentInstanceId: RECORD_INDEX_FOCUS_ID, + }, + globalHotkeysConfig: { + enableGlobalHotkeysWithModifiers: true, + enableGlobalHotkeysConflictingWithKeyboard: true, + }, + memoizeKey: 'global', + }, + hotkeyScope: { + scope: RecordIndexHotkeyScope.RecordIndex, + customScopes: { + goto: true, + keyboardShortcutMenu: true, + searchRecords: true, + }, + }, + }); + }; + + return { + resetFocusStackToRecordIndex, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyEffectsWrapper.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyEffectsWrapper.tsx index 557d998c0..70f500e30 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyEffectsWrapper.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyEffectsWrapper.tsx @@ -2,11 +2,8 @@ import { RecordTableDeactivateRecordTableRowEffect } from '@/object-record/recor import { RecordTableBodyEscapeHotkeyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyEscapeHotkeyEffect'; import { RecordTableBodyFocusClickOutsideEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyFocusClickOutsideEffect'; import { RecordTableBodyFocusKeyboardEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyFocusKeyboardEffect'; -import { RecordTableBodyRowFocusKeyboardEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyRowFocusKeyboardEffect'; import { RecordTableNoRecordGroupBodyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect'; import { RecordTableRecordGroupBodyEffects } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffects'; -import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; export interface RecordTableBodyEffectsWrapperProps { hasRecordGroups: boolean; @@ -17,10 +14,6 @@ export const RecordTableBodyEffectsWrapper = ({ hasRecordGroups, tableBodyRef, }: RecordTableBodyEffectsWrapperProps) => { - const isRecordTableRowFocusActive = useRecoilComponentValueV2( - isRecordTableRowFocusActiveComponentState, - ); - return ( <> {hasRecordGroups ? ( @@ -30,7 +23,6 @@ export const RecordTableBodyEffectsWrapper = ({ )} - {isRecordTableRowFocusActive && } diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableDeactivateRecordTableRowEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableDeactivateRecordTableRowEffect.tsx index 4eabda7f7..f666a3229 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableDeactivateRecordTableRowEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableDeactivateRecordTableRowEffect.tsx @@ -1,10 +1,10 @@ import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow'; -import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose'; +import { useListenToSidePanelClosing } from '@/ui/layout/right-drawer/hooks/useListenToSidePanelClosing'; export const RecordTableDeactivateRecordTableRowEffect = () => { const { deactivateRecordTableRow } = useActiveRecordTableRow(); - useListenRightDrawerClose(() => { + useListenToSidePanelClosing(() => { deactivateRecordTableRow(); }); diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableFocusModeHotkeysSetterEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableFocusModeHotkeysSetterEffect.tsx deleted file mode 100644 index 2e09b9841..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableFocusModeHotkeysSetterEffect.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useContext } from 'react'; -import { Key } from 'ts-key-enum'; - -import { useClearField } from '@/object-record/record-field/hooks/useClearField'; -import { useIsFieldClearable } from '@/object-record/record-field/hooks/useIsFieldClearable'; -import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly'; -import { useToggleEditOnlyInput } from '@/object-record/record-field/hooks/useToggleEditOnlyInput'; -import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell'; -import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey'; - -import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; -import { TableHotkeyScope } from '../types/TableHotkeyScope'; - -export const RecordTableFocusModeHotkeysSetterEffect = () => { - const { openTableCell } = useOpenRecordTableCellFromCell(); - const { isReadOnly } = useContext(FieldContext); - - const isFieldInputOnly = useIsFieldInputOnly(); - - const isFieldClearable = useIsFieldClearable(); - - const toggleEditOnlyInput = useToggleEditOnlyInput(); - - const clearField = useClearField(); - - useScopedHotkeys( - [Key.Backspace, Key.Delete], - () => { - if (!isFieldInputOnly && isFieldClearable) { - clearField(); - } - }, - TableHotkeyScope.TableFocus, - [clearField, isFieldClearable, isFieldInputOnly], - ); - - useScopedHotkeys( - Key.Enter, - () => { - if (isReadOnly) { - return; - } - - if (!isFieldInputOnly) { - openTableCell(); - } else { - toggleEditOnlyInput(); - } - }, - TableHotkeyScope.TableFocus, - [openTableCell, isFieldInputOnly, toggleEditOnlyInput, isReadOnly], - ); - - useScopedHotkeys( - '*', - (keyboardEvent) => { - if (isReadOnly) { - return; - } - - if (!isFieldInputOnly) { - const isWritingText = - !isNonTextWritingKey(keyboardEvent.key) && - !keyboardEvent.ctrlKey && - !keyboardEvent.metaKey; - - if (!isWritingText) { - return; - } - - keyboardEvent.preventDefault(); - keyboardEvent.stopPropagation(); - keyboardEvent.stopImmediatePropagation(); - - openTableCell(keyboardEvent.key); - } - }, - TableHotkeyScope.TableFocus, - [openTableCell, isFieldInputOnly, toggleEditOnlyInput, isReadOnly], - { - preventDefault: false, - }, - ); - - return <>; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRecordGroupBodyContextProvider.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRecordGroupBodyContextProvider.tsx index 68a89348d..b5b020556 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRecordGroupBodyContextProvider.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRecordGroupBodyContextProvider.tsx @@ -1,7 +1,7 @@ import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter'; -import { useRecordTableMove } from '@/object-record/record-table/hooks/useRecordTableMove'; +import { useRecordTableMoveFocusedCell } from '@/object-record/record-table/hooks/useRecordTableMoveFocusedCell'; import { useCloseRecordTableCellInGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup'; import { useMoveHoverToCurrentCell } from '@/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell'; import { @@ -29,10 +29,10 @@ export const RecordTableRecordGroupBodyContextProvider = ({ openTableCell(args); }; - const { move } = useRecordTableMove(recordTableId); + const { moveFocus } = useRecordTableMoveFocusedCell(recordTableId); const handleMoveFocus = (direction: MoveFocusDirection) => { - move(direction); + moveFocus(direction); }; const { closeTableCellInGroup } = useCloseRecordTableCellInGroup(); diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx index 16e71e5c7..ac8e44b62 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx @@ -13,7 +13,7 @@ import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinit import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; +import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext'; import { useRecordTable } from '../hooks/useRecordTable'; @@ -58,15 +58,16 @@ export const RecordTableWithWrappers = ({ }, ); - useScopedHotkeys( - 'ctrl+a,meta+a', - handleSelectAllRows, - TableHotkeyScope.TableFocus, - [], - { + useHotkeysOnFocusedElement({ + keys: ['ctrl+a,meta+a'], + callback: handleSelectAllRows, + focusId: recordTableId, + scope: RecordIndexHotkeyScope.RecordIndex, + dependencies: [handleSelectAllRows], + options: { enableOnFormTags: false, }, - ); + }); const { saveViewFields } = useSaveCurrentViewFields(); diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode.ts index f4c1806a3..175923cea 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode.ts @@ -2,6 +2,8 @@ import { useRecoilCallback } from 'recoil'; import { recordTableCellEditModePositionComponentState } from '@/object-record/record-table/states/recordTableCellEditModePositionComponentState'; import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId'; +import { useRemoveLastFocusItemFromFocusStackByComponentType } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackByComponentType'; +import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; export const useCloseCurrentTableCellInEditMode = (recordTableId?: string) => { @@ -14,14 +16,25 @@ export const useCloseCurrentTableCellInEditMode = (recordTableId?: string) => { const { goBackToPreviousDropdownFocusId } = useGoBackToPreviousDropdownFocusId(); + const { removeLastFocusItemFromFocusStackByComponentType } = + useRemoveLastFocusItemFromFocusStackByComponentType(); + return useRecoilCallback( ({ set }) => { - return async () => { + return () => { set(currentTableCellInEditModePositionState, null); goBackToPreviousDropdownFocusId(); + + removeLastFocusItemFromFocusStackByComponentType({ + componentType: FocusComponentType.OPEN_FIELD_INPUT, + }); }; }, - [currentTableCellInEditModePositionState, goBackToPreviousDropdownFocusId], + [ + currentTableCellInEditModePositionState, + goBackToPreviousDropdownFocusId, + removeLastFocusItemFromFocusStackByComponentType, + ], ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useLeaveTableFocus.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useLeaveTableFocus.ts index 1fd332a35..6ff315acf 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useLeaveTableFocus.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useLeaveTableFocus.ts @@ -1,7 +1,8 @@ +import { useResetFocusStackToRecordIndex } from '@/object-record/record-index/hooks/useResetFocusStackToRecordIndex'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow'; import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; -import { useSetIsRecordTableFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive'; +import { useUnfocusRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useUnfocusRecordTableCell'; import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; @@ -17,10 +18,6 @@ export const useLeaveTableFocus = (recordTableId?: string) => { recordTableIdFromContext, ); - const { setIsFocusActiveForCurrentPosition } = useSetIsRecordTableFocusActive( - recordTableIdFromContext, - ); - const setRecordTableHoverPosition = useSetRecoilComponentStateV2( recordTableHoverPositionComponentState, recordTableIdFromContext, @@ -34,15 +31,23 @@ export const useLeaveTableFocus = (recordTableId?: string) => { recordTableIdFromContext, ); - return () => { - resetTableRowSelection(); + const { resetFocusStackToRecordIndex } = useResetFocusStackToRecordIndex(); - setIsFocusActiveForCurrentPosition(false); + const { unfocusRecordTableCell } = useUnfocusRecordTableCell( + recordTableIdFromContext, + ); + + return () => { + unfocusRecordTableCell(); + + resetTableRowSelection(); unfocusRecordTableRow(); deactivateRecordTableRow(); setRecordTableHoverPosition(null); + + resetFocusStackToRecordIndex(); }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts index b01d4ba82..42cd1e1d4 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts @@ -4,7 +4,8 @@ import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { useSetIsRecordTableFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive'; +import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; +import { useUnfocusRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useUnfocusRecordTableCell'; import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState'; import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState'; import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState'; @@ -44,14 +45,14 @@ export const useSetRecordTableData = ({ recordTableId, ); - const { setIsFocusActiveForCurrentPosition } = - useSetIsRecordTableFocusActive(recordTableId); - const setRecordTableHoverPosition = useSetRecoilComponentStateV2( recordTableHoverPositionComponentState, recordTableId, ); + const { unfocusRecordTableCell } = useUnfocusRecordTableCell(recordTableId); + const { unfocusRecordTableRow } = useFocusedRecordTableRow(recordTableId); + return useRecoilCallback( ({ set, snapshot }) => ({ @@ -92,7 +93,8 @@ export const useSetRecordTableData = ({ const recordIds = records.map((record) => record.id); if (!isDeeplyEqual(currentRowIds, recordIds)) { - setIsFocusActiveForCurrentPosition(false); + unfocusRecordTableCell(); + unfocusRecordTableRow(); setRecordTableHoverPosition(null); if (hasUserSelectedAllRows) { @@ -115,7 +117,8 @@ export const useSetRecordTableData = ({ recordIndexRecordIdsByGroupFamilyState, recordIndexAllRecordIdsSelector, hasUserSelectedAllRowsState, - setIsFocusActiveForCurrentPosition, + unfocusRecordTableCell, + unfocusRecordTableRow, setRecordTableHoverPosition, isRowSelectedFamilyState, ], diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableFocusPosition.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableFocusPosition.ts deleted file mode 100644 index 520920620..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableFocusPosition.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useRecoilCallback } from 'recoil'; - -import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState'; -import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; -import { useSetIsRecordTableFocusActive } from '../../record-table-cell/hooks/useSetIsRecordTableFocusActive'; -import { TableCellPosition } from '../../types/TableCellPosition'; - -export const useSetRecordTableFocusPosition = (recordTableId?: string) => { - const focusPositionState = useRecoilComponentCallbackStateV2( - recordTableFocusPositionComponentState, - recordTableId, - ); - - const { setIsFocusActive, setIsFocusActiveForCurrentPosition } = - useSetIsRecordTableFocusActive(recordTableId); - - return useRecoilCallback( - ({ set }) => { - return (newPosition: TableCellPosition) => { - set(focusPositionState, newPosition); - - setIsFocusActiveForCurrentPosition(false); - setIsFocusActive(true, newPosition); - }; - }, - [focusPositionState, setIsFocusActive, setIsFocusActiveForCurrentPosition], - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useFocusedRecordTableRow.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useFocusedRecordTableRow.ts index c05921d24..753a7518c 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useFocusedRecordTableRow.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useFocusedRecordTableRow.ts @@ -1,36 +1,58 @@ +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; +import { useUnfocusRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useUnfocusRecordTableCell'; +import { getRecordTableRowFocusId } from '@/object-record/record-table/record-table-row/utils/getRecordTableRowFocusId'; +import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { focusedRecordTableRowIndexComponentState } from '@/object-record/record-table/states/focusedRecordTableRowIndexComponentState'; import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState'; import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState'; import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState'; import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState'; +import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; +import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; +import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const useFocusedRecordTableRow = (recordTableId?: string) => { + const recordTableIdFromContext = useAvailableComponentInstanceIdOrThrow( + RecordTableComponentInstanceContext, + recordTableId, + ); + const isRowFocusedState = useRecoilComponentCallbackStateV2( isRecordTableRowFocusedComponentFamilyState, - recordTableId, + recordTableIdFromContext, ); const focusedRowIndexState = useRecoilComponentCallbackStateV2( focusedRecordTableRowIndexComponentState, - recordTableId, + recordTableIdFromContext, ); const isRowFocusActiveState = useRecoilComponentCallbackStateV2( isRecordTableRowFocusActiveComponentState, - recordTableId, + recordTableIdFromContext, ); const focusedCellPositionState = useRecoilComponentCallbackStateV2( recordTableFocusPositionComponentState, - recordTableId, + recordTableIdFromContext, ); const isRecordTableCellFocusActiveState = useRecoilComponentCallbackStateV2( isRecordTableCellFocusActiveComponentState, - recordTableId, + recordTableIdFromContext, + ); + + const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); + + const { removeFocusItemFromFocusStackById } = + useRemoveFocusItemFromFocusStackById(); + + const { unfocusRecordTableCell } = useUnfocusRecordTableCell( + recordTableIdFromContext, ); const unfocusRecordTableRow = useRecoilCallback( @@ -44,11 +66,26 @@ export const useFocusedRecordTableRow = (recordTableId?: string) => { return; } + const focusId = getRecordTableRowFocusId({ + recordTableId: recordTableIdFromContext, + rowIndex: focusedRowIndex, + }); + + removeFocusItemFromFocusStackById({ + focusId, + }); + set(focusedRowIndexState, null); set(isRowFocusedState(focusedRowIndex), false); set(isRowFocusActiveState, false); }, - [focusedRowIndexState, isRowFocusedState, isRowFocusActiveState], + [ + focusedRowIndexState, + isRowFocusedState, + isRowFocusActiveState, + recordTableIdFromContext, + removeFocusItemFromFocusStackById, + ], ); const focusRecordTableRow = useRecoilCallback( @@ -60,13 +97,51 @@ export const useFocusedRecordTableRow = (recordTableId?: string) => { if (isDefined(focusedRowIndex) && focusedRowIndex !== rowIndex) { set(isRowFocusedState(focusedRowIndex), false); + + const focusId = getRecordTableRowFocusId({ + recordTableId: recordTableIdFromContext, + rowIndex: focusedRowIndex, + }); + + removeFocusItemFromFocusStackById({ + focusId, + }); } + const focusId = getRecordTableRowFocusId({ + recordTableId: recordTableIdFromContext, + rowIndex, + }); + + pushFocusItemToFocusStack({ + focusId, + component: { + type: FocusComponentType.RECORD_TABLE_ROW, + instanceId: focusId, + }, + hotkeyScope: { + scope: RecordIndexHotkeyScope.RecordIndex, + customScopes: { + goto: true, + keyboardShortcutMenu: true, + searchRecords: true, + }, + }, + memoizeKey: focusId, + }); + set(focusedRowIndexState, rowIndex); set(isRowFocusedState(rowIndex), true); set(isRowFocusActiveState, true); }, - [focusedRowIndexState, isRowFocusedState, isRowFocusActiveState], + [ + focusedRowIndexState, + recordTableIdFromContext, + pushFocusItemToFocusStack, + isRowFocusedState, + isRowFocusActiveState, + removeFocusItemFromFocusStackById, + ], ); const restoreRecordTableRowFocusFromCellPosition = useRecoilCallback( @@ -84,20 +159,21 @@ export const useFocusedRecordTableRow = (recordTableId?: string) => { .getLoadable(isRecordTableCellFocusActiveState) .getValue(); - if ( - !isDefined(focusedRowIndex) || - !isDefined(focusedCellPosition) || - !isRecordTableCellFocusActive - ) { + if (!isDefined(focusedCellPosition) || !isRecordTableCellFocusActive) { return; } - focusRecordTableRow(focusedCellPosition.row); + unfocusRecordTableCell(); + + if (isDefined(focusedRowIndex)) { + focusRecordTableRow(focusedCellPosition.row); + } }, [ focusedRowIndexState, focusedCellPositionState, isRecordTableCellFocusActiveState, + unfocusRecordTableCell, focusRecordTableRow, ], ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useMapKeyboardToFocus.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useMapKeyboardToFocus.ts deleted file mode 100644 index 7311849d2..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useMapKeyboardToFocus.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; -import { useRecordTableMove } from '@/object-record/record-table/hooks/useRecordTableMove'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; -import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; -import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { Key } from 'ts-key-enum'; - -const TABLE_NAVIGATION_CUSTOM_SCOPES = { - goto: true, - keyboardShortcutMenu: true, - searchRecords: true, -}; - -export const useMapKeyboardToFocus = (recordTableId?: string) => { - const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(); - - const { move } = useRecordTableMove(recordTableId); - - useScopedHotkeys( - [Key.ArrowUp, `${Key.Shift}+${Key.Enter}`], - () => { - move('up'); - }, - TableHotkeyScope.TableFocus, - [move], - ); - - useScopedHotkeys( - Key.ArrowDown, - () => { - move('down'); - }, - TableHotkeyScope.TableFocus, - [move], - ); - - useScopedHotkeys( - [Key.ArrowUp], - () => { - setHotkeyScopeAndMemorizePreviousScope({ - scope: TableHotkeyScope.TableFocus, - customScopes: TABLE_NAVIGATION_CUSTOM_SCOPES, - }); - move('up'); - }, - RecordIndexHotkeyScope.RecordIndex, - [move], - ); - - useScopedHotkeys( - [Key.ArrowDown], - () => { - setHotkeyScopeAndMemorizePreviousScope({ - scope: TableHotkeyScope.TableFocus, - customScopes: TABLE_NAVIGATION_CUSTOM_SCOPES, - }); - move('down'); - }, - RecordIndexHotkeyScope.RecordIndex, - [move], - ); - - useScopedHotkeys( - [Key.ArrowLeft, `${Key.Shift}+${Key.Tab}`], - () => { - move('left'); - }, - TableHotkeyScope.TableFocus, - [move], - ); - - useScopedHotkeys( - [Key.ArrowRight, Key.Tab], - () => { - move('right'); - }, - TableHotkeyScope.TableFocus, - [move], - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts index 739c8e8dd..8b1875e86 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts @@ -12,7 +12,6 @@ import { RecordTableComponentInstanceContext } from '@/object-record/record-tabl import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState'; import { onColumnsChangeComponentState } from '@/object-record/record-table/states/onColumnsChangeComponentState'; -import { useRecordTableMove } from '@/object-record/record-table/hooks/useRecordTableMove'; import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; @@ -20,7 +19,6 @@ import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-sta import { useLeaveTableFocus } from './internal/useLeaveTableFocus'; import { useResetTableRowSelection } from './internal/useResetTableRowSelection'; import { useSelectAllRows } from './internal/useSelectAllRows'; -import { useSetRecordTableFocusPosition } from './internal/useSetRecordTableFocusPosition'; import { useSetRowSelectedState } from './internal/useSetRowSelectedState'; type useRecordTableProps = { recordTableId?: string; @@ -94,10 +92,6 @@ export const useRecordTable = (props?: useRecordTableProps) => { const resetTableRowSelection = useResetTableRowSelection(recordTableId); - const setFocusPosition = useSetRecordTableFocusPosition(recordTableId); - - const { move } = useRecordTableMove(recordTableId); - const { selectAllRows } = useSelectAllRows(recordTableId); return { @@ -106,11 +100,9 @@ export const useRecordTable = (props?: useRecordTableProps) => { leaveTableFocus, setRowSelected, resetTableRowSelection, - move, selectAllRows, setOnColumnsChange, setIsRecordTableInitialLoading, - setFocusPosition, setHasUserSelectedAllRows, setOnToggleColumnSort, }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMove.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMove.ts deleted file mode 100644 index 3c66d6033..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMove.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { useRecordTableMoveFocusedCell } from '@/object-record/record-table/hooks/useRecordTableMoveFocusedCell'; -import { useRecordTableMoveFocusedRow } from '@/object-record/record-table/hooks/useRecordTableMoveFocusedRow'; -import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState'; -import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection'; -import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; -import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; -import { useRecoilCallback } from 'recoil'; - -export const useRecordTableMove = (recordTableId?: string) => { - const { moveFocusedRow } = useRecordTableMoveFocusedRow(recordTableId); - - const { moveFocus } = useRecordTableMoveFocusedCell(recordTableId); - - const isRecordTableFocusActiveState = useRecoilComponentCallbackStateV2( - isRecordTableCellFocusActiveComponentState, - recordTableId, - ); - - const move = useRecoilCallback( - ({ snapshot }) => - (direction: MoveFocusDirection) => { - const isRecordTableFocusActive = getSnapshotValue( - snapshot, - isRecordTableFocusActiveState, - ); - - if (isRecordTableFocusActive) { - moveFocus(direction); - } else { - moveFocusedRow(direction); - } - }, - [isRecordTableFocusActiveState, moveFocusedRow, moveFocus], - ); - - return { - move, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocusedCell.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocusedCell.ts index 43f8062f2..1db85a246 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocusedCell.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocusedCell.ts @@ -4,13 +4,13 @@ import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocus import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; -import { useSetRecordTableFocusPosition } from '@/object-record/record-table/hooks/internal/useSetRecordTableFocusPosition'; +import { useFocusRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useFocusRecordTableCell'; import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState'; import { numberOfTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/numberOfTableColumnsComponentSelector'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; export const useRecordTableMoveFocusedCell = (recordTableId?: string) => { - const setFocusPosition = useSetRecordTableFocusPosition(recordTableId); + const { focusRecordTableCell } = useFocusRecordTableCell(recordTableId); const focusPositionState = useRecoilComponentCallbackStateV2( recordTableFocusPositionComponentState, @@ -33,12 +33,12 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => { newRowIndex = 0; } - setFocusPosition({ + focusRecordTableCell({ ...focusPosition, row: newRowIndex, }); }, - [focusPositionState, setFocusPosition], + [focusPositionState, focusRecordTableCell], ); const moveDown = useRecoilCallback( @@ -56,12 +56,12 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => { newRowIndex = allRecordIds.length - 1; } - setFocusPosition({ + focusRecordTableCell({ ...focusPosition, row: newRowIndex, }); }, - [recordIndexAllRecordIdsSelector, setFocusPosition, focusPositionState], + [recordIndexAllRecordIdsSelector, focusRecordTableCell, focusPositionState], ); const numberOfTableColumnsSelector = useRecoilComponentCallbackStateV2( @@ -101,12 +101,12 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => { } if (isNotLastColumn) { - setFocusPosition({ + focusRecordTableCell({ row: currentRowIndex, column: currentColumnIndex + 1, }); } else if (isLastColumnButNotLastRow) { - setFocusPosition({ + focusRecordTableCell({ row: currentRowIndex + 1, column: 0, }); @@ -116,7 +116,7 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => { recordIndexAllRecordIdsSelector, focusPositionState, numberOfTableColumnsSelector, - setFocusPosition, + focusRecordTableCell, ], ); @@ -146,18 +146,18 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => { } if (isNotFirstColumn) { - setFocusPosition({ + focusRecordTableCell({ row: currentRowIndex, column: currentColumnIndex - 1, }); } else if (isFirstColumnButNotFirstRow) { - setFocusPosition({ + focusRecordTableCell({ row: currentRowIndex - 1, column: numberOfTableColumns - 1, }); } }, - [numberOfTableColumnsSelector, focusPositionState, setFocusPosition], + [numberOfTableColumnsSelector, focusPositionState, focusRecordTableCell], ); const moveFocus = (direction: MoveFocusDirection) => { @@ -182,7 +182,6 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => { moveLeft, moveRight, moveUp, - setFocusPosition, moveFocus, }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableRowFocusHotkeys.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableRowFocusHotkeys.ts new file mode 100644 index 000000000..07f22b7a9 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableRowFocusHotkeys.ts @@ -0,0 +1,36 @@ +import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; +import { useRecordTableMoveFocusedRow } from '@/object-record/record-table/hooks/useRecordTableMoveFocusedRow'; +import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement'; +import { Key } from 'ts-key-enum'; + +export const useRecordTableRowFocusHotkeys = ({ + focusId, + hotkeyScope, +}: { + focusId: string; + hotkeyScope: string; +}) => { + const { recordTableId } = useRecordTableContextOrThrow(); + + const { moveFocusedRow } = useRecordTableMoveFocusedRow(recordTableId); + + useHotkeysOnFocusedElement({ + keys: [Key.ArrowUp, `${Key.Shift}+${Key.Enter}`, 'k'], + callback: () => { + moveFocusedRow('up'); + }, + focusId, + scope: hotkeyScope, + dependencies: [moveFocusedRow], + }); + + useHotkeysOnFocusedElement({ + keys: [Key.ArrowDown, 'j'], + callback: () => { + moveFocusedRow('down'); + }, + focusId, + scope: hotkeyScope, + dependencies: [moveFocusedRow], + }); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useTableColumns.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useTableColumns.ts index 707429be0..de9026166 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useTableColumns.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useTableColumns.ts @@ -2,6 +2,7 @@ import { useCallback } from 'react'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; +import { useUnfocusRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useUnfocusRecordTableCell'; import { useMoveViewColumns } from '@/views/hooks/useMoveViewColumns'; import { useSetTableColumns } from '@/object-record/record-table/hooks/useSetTableColumns'; @@ -40,6 +41,10 @@ export const useTableColumns = (props?: useRecordTableProps) => { const { handleColumnMove } = useMoveViewColumns(); + const { unfocusRecordTableCell } = useUnfocusRecordTableCell( + props?.recordTableId, + ); + const instanceId = useAvailableComponentInstanceIdOrThrow( RecordTableComponentInstanceContext, props?.recordTableId, @@ -106,6 +111,8 @@ export const useTableColumns = (props?: useRecordTableProps) => { direction: 'left' | 'right', column: ColumnDefinition, ) => { + unfocusRecordTableCell(); + const currentColumnArrayIndex = visibleTableColumns.findIndex( (visibleColumn) => visibleColumn.fieldMetadataId === column.fieldMetadataId, @@ -119,7 +126,12 @@ export const useTableColumns = (props?: useRecordTableProps) => { await handleColumnsChange(columns); }, - [visibleTableColumns, handleColumnMove, handleColumnsChange], + [ + unfocusRecordTableCell, + visibleTableColumns, + handleColumnMove, + handleColumnsChange, + ], ); const handleColumnReorder = useCallback( diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyEscapeHotkeyEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyEscapeHotkeyEffect.tsx index 78c3edf07..0ec1f1a04 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyEscapeHotkeyEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyEscapeHotkeyEffect.tsx @@ -1,11 +1,11 @@ import { Key } from 'ts-key-enum'; +import { RECORD_INDEX_FOCUS_ID } from '@/object-record/record-index/constants/RecordIndexFocusId'; import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { isAtLeastOneTableRowSelectedSelector } from '@/object-record/record-table/record-table-row/states/isAtLeastOneTableRowSelectedSelector'; -import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; export const RecordTableBodyEscapeHotkeyEffect = () => { @@ -15,23 +15,26 @@ export const RecordTableBodyEscapeHotkeyEffect = () => { recordTableId, }); - const { unfocusRecordTableRow } = useFocusedRecordTableRow(recordTableId); - const isAtLeastOneRecordSelected = useRecoilComponentValueV2( isAtLeastOneTableRowSelectedSelector, ); - useScopedHotkeys( - [Key.Escape], - () => { - unfocusRecordTableRow(); - if (isAtLeastOneRecordSelected) { - resetTableRowSelection(); - } + const handleEscape = () => { + if (isAtLeastOneRecordSelected) { + resetTableRowSelection(); + } + }; + + useHotkeysOnFocusedElement({ + keys: [Key.Escape], + callback: handleEscape, + focusId: RECORD_INDEX_FOCUS_ID, + scope: RecordIndexHotkeyScope.RecordIndex, + dependencies: [handleEscape], + options: { + preventDefault: true, }, - RecordIndexHotkeyScope.RecordIndex, - [isAtLeastOneRecordSelected, resetTableRowSelection, unfocusRecordTableRow], - ); + }); return null; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyFocusClickOutsideEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyFocusClickOutsideEffect.tsx index 5d6229b1e..13a390c59 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyFocusClickOutsideEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyFocusClickOutsideEffect.tsx @@ -4,10 +4,8 @@ import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/Recor import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useLeaveTableFocus } from '@/object-record/record-table/hooks/internal/useLeaveTableFocus'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { MODAL_BACKDROP_CLICK_OUTSIDE_ID } from '@/ui/layout/modal/constants/ModalBackdropClickOutsideId'; import { PAGE_ACTION_CONTAINER_CLICK_OUTSIDE_ID } from '@/ui/layout/page/constants/PageActionContainerClickOutsideId'; -import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useRecoilValue } from 'recoil'; @@ -24,8 +22,6 @@ export const RecordTableBodyFocusClickOutsideEffect = ({ const currentHotkeyScope = useRecoilValue(currentHotkeyScopeState); - const setHotkeyScope = useSetHotkeyScope(); - useListenClickOutside({ excludedClickOutsideIds: [ ACTION_MENU_DROPDOWN_CLICK_OUTSIDE_ID, @@ -36,19 +32,11 @@ export const RecordTableBodyFocusClickOutsideEffect = ({ listenerId: RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID, refs: [tableBodyRef], callback: () => { - if ( - currentHotkeyScope.scope !== TableHotkeyScope.TableFocus && - currentHotkeyScope.scope !== RecordIndexHotkeyScope.RecordIndex - ) { + if (currentHotkeyScope.scope !== RecordIndexHotkeyScope.RecordIndex) { return; } leaveTableFocus(); - setHotkeyScope(RecordIndexHotkeyScope.RecordIndex, { - goto: true, - keyboardShortcutMenu: true, - searchRecords: true, - }); }, }); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyFocusKeyboardEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyFocusKeyboardEffect.tsx index b0d74609c..13d266259 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyFocusKeyboardEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyFocusKeyboardEffect.tsx @@ -1,54 +1,12 @@ +import { RECORD_INDEX_FOCUS_ID } from '@/object-record/record-index/constants/RecordIndexFocusId'; import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; -import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; -import { useMapKeyboardToFocus } from '@/object-record/record-table/hooks/useMapKeyboardToFocus'; -import { useSetIsRecordTableFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive'; -import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; -import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { Key } from 'ts-key-enum'; +import { useRecordTableRowFocusHotkeys } from '@/object-record/record-table/hooks/useRecordTableRowFocusHotkeys'; export const RecordTableBodyFocusKeyboardEffect = () => { - const { recordTableId } = useRecordTableContextOrThrow(); + useRecordTableRowFocusHotkeys({ + focusId: RECORD_INDEX_FOCUS_ID, + hotkeyScope: RecordIndexHotkeyScope.RecordIndex, + }); - const setHotkeyScope = useSetHotkeyScope(); - - const { restoreRecordTableRowFocusFromCellPosition } = - useFocusedRecordTableRow(recordTableId); - - const { setIsFocusActiveForCurrentPosition } = - useSetIsRecordTableFocusActive(recordTableId); - - const isRecordTableFocusActive = useRecoilComponentValueV2( - isRecordTableCellFocusActiveComponentState, - ); - - useMapKeyboardToFocus(recordTableId); - - useScopedHotkeys( - [Key.Escape], - () => { - if (isRecordTableFocusActive) { - restoreRecordTableRowFocusFromCellPosition(); - setIsFocusActiveForCurrentPosition(false); - } else { - setHotkeyScope(RecordIndexHotkeyScope.RecordIndex, { - goto: true, - keyboardShortcutMenu: true, - searchRecords: true, - }); - } - }, - TableHotkeyScope.TableFocus, - [ - setIsFocusActiveForCurrentPosition, - restoreRecordTableRowFocusFromCellPosition, - setHotkeyScope, - isRecordTableFocusActive, - ], - ); - - return <>; + return null; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyRowFocusKeyboardEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyRowFocusKeyboardEffect.tsx deleted file mode 100644 index 61afee3cc..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyRowFocusKeyboardEffect.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { useRecordTableMoveFocusedRow } from '@/object-record/record-table/hooks/useRecordTableMoveFocusedRow'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; -import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { Key } from 'ts-key-enum'; - -export const RecordTableBodyRowFocusKeyboardEffect = () => { - const { recordTableId } = useRecordTableContextOrThrow(); - - const { moveFocusedRow } = useRecordTableMoveFocusedRow(recordTableId); - - useScopedHotkeys( - [Key.ArrowUp, 'k'], - () => { - moveFocusedRow('up'); - }, - TableHotkeyScope.TableFocus, - [moveFocusedRow], - ); - - useScopedHotkeys( - [Key.ArrowDown, 'j'], - () => { - moveFocusedRow('down'); - }, - TableHotkeyScope.TableFocus, - [moveFocusedRow], - ); - - return <>; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellArrowKeysEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellArrowKeysEffect.tsx new file mode 100644 index 000000000..3166a50a2 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellArrowKeysEffect.tsx @@ -0,0 +1,14 @@ +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; +import { useCurrentlyFocusedRecordTableCellFocusId } from '@/object-record/record-table/record-table-cell/hooks/useCurrentlyFocusedRecordTableCellFocusId'; +import { useRecordTableCellFocusHotkeys } from '@/object-record/record-table/record-table-cell/hooks/useRecordTableCellFocusHotkeys'; + +export const RecordTableCellArrowKeysEffect = () => { + const recordTableCellFocusId = useCurrentlyFocusedRecordTableCellFocusId(); + + useRecordTableCellFocusHotkeys({ + focusId: recordTableCellFocusId, + hotkeyScope: RecordIndexHotkeyScope.RecordIndex, + }); + + return null; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellEditMode.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellEditMode.tsx index 2971f2c12..512915f9e 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellEditMode.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellEditMode.tsx @@ -5,7 +5,7 @@ import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/r import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState'; import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; -import { useSetRecordTableFocusPosition } from '@/object-record/record-table/hooks/internal/useSetRecordTableFocusPosition'; +import { useFocusRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useFocusRecordTableCell'; import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; @@ -93,7 +93,7 @@ export const RecordTableCellEditMode = ({ const { cellPosition } = useContext(RecordTableCellContext); - const setFocusPosition = useSetRecordTableFocusPosition(); + const { focusRecordTableCell } = useFocusRecordTableCell(); return ( { - setFocusPosition(cellPosition); + focusRecordTableCell(cellPosition); }} > {children} diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellEditModePortal.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellEditModePortal.tsx index 49384d00d..0046a87fb 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellEditModePortal.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellEditModePortal.tsx @@ -1,9 +1,9 @@ import { RecordTableCellPortalWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellPortalWrapper'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { RecordTableFocusModeHotkeysSetterEffect } from '@/object-record/record-table/components/RecordTableFocusModeHotkeysSetterEffect'; import { RecordTableCellEditMode } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditMode'; import { RecordTableCellFieldInput } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldInput'; +import { RecordTableCellHotkeysEffect } from '@/object-record/record-table/record-table-cell/components/RecordTableCellHotkeysEffect'; import { recordTableCellEditModePositionComponentState } from '@/object-record/record-table/states/recordTableCellEditModePositionComponentState'; import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState'; import styled from '@emotion/styled'; @@ -38,7 +38,7 @@ export const RecordTableCellEditModePortal = () => { )} - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellHotkeysEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellHotkeysEffect.tsx new file mode 100644 index 000000000..57f9b5df1 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellHotkeysEffect.tsx @@ -0,0 +1,118 @@ +import { useContext } from 'react'; +import { Key } from 'ts-key-enum'; + +import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; +import { useClearField } from '@/object-record/record-field/hooks/useClearField'; +import { useIsFieldClearable } from '@/object-record/record-field/hooks/useIsFieldClearable'; +import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly'; +import { useToggleEditOnlyInput } from '@/object-record/record-field/hooks/useToggleEditOnlyInput'; +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; +import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext'; +import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; +import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; +import { useCurrentlyFocusedRecordTableCellFocusId } from '@/object-record/record-table/record-table-cell/hooks/useCurrentlyFocusedRecordTableCellFocusId'; +import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell'; +import { useListenToSidePanelOpening } from '@/ui/layout/right-drawer/hooks/useListenToSidePanelOpening'; +import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement'; +import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey'; + +export const RecordTableCellHotkeysEffect = () => { + const { openTableCell } = useOpenRecordTableCellFromCell(); + const { isReadOnly } = useContext(FieldContext); + const cellFocusId = useCurrentlyFocusedRecordTableCellFocusId(); + const { onCloseTableCell } = useRecordTableBodyContextOrThrow(); + + const isFieldInputOnly = useIsFieldInputOnly(); + const isFieldClearable = useIsFieldClearable(); + const toggleEditOnlyInput = useToggleEditOnlyInput(); + const clearField = useClearField(); + + const handleBackspaceOrDelete = () => { + if (!isFieldInputOnly && isFieldClearable) { + clearField(); + } + }; + + const handleEnter = () => { + if (isReadOnly) { + return; + } + + if (!isFieldInputOnly) { + openTableCell(); + } else { + toggleEditOnlyInput(); + } + }; + + const handleAnyKey = (keyboardEvent: KeyboardEvent) => { + if (isReadOnly) { + return; + } + + if (!isFieldInputOnly) { + const isWritingText = + !isNonTextWritingKey(keyboardEvent.key) && + !keyboardEvent.ctrlKey && + !keyboardEvent.metaKey; + + if (!isWritingText) { + return; + } + + keyboardEvent.preventDefault(); + keyboardEvent.stopPropagation(); + keyboardEvent.stopImmediatePropagation(); + + openTableCell(keyboardEvent.key); + } + }; + + const { recordTableId } = useRecordTableContextOrThrow(); + + const { restoreRecordTableRowFocusFromCellPosition } = + useFocusedRecordTableRow(recordTableId); + + const handleEscape = () => { + restoreRecordTableRowFocusFromCellPosition(); + }; + + useListenToSidePanelOpening(() => onCloseTableCell()); + + useHotkeysOnFocusedElement({ + keys: [Key.Backspace, Key.Delete], + callback: handleBackspaceOrDelete, + focusId: cellFocusId, + scope: RecordIndexHotkeyScope.RecordIndex, + dependencies: [handleBackspaceOrDelete], + }); + + useHotkeysOnFocusedElement({ + keys: [Key.Enter], + callback: handleEnter, + focusId: cellFocusId, + scope: RecordIndexHotkeyScope.RecordIndex, + dependencies: [handleEnter], + }); + + useHotkeysOnFocusedElement({ + keys: [Key.Escape], + callback: handleEscape, + focusId: cellFocusId, + scope: RecordIndexHotkeyScope.RecordIndex, + dependencies: [handleEscape], + }); + + useHotkeysOnFocusedElement({ + keys: ['*'], + callback: handleAnyKey, + focusId: cellFocusId, + scope: RecordIndexHotkeyScope.RecordIndex, + dependencies: [handleAnyKey], + options: { + preventDefault: false, + }, + }); + + return null; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellPortals.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellPortals.tsx index 48097b0e7..1a98022fb 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellPortals.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellPortals.tsx @@ -1,4 +1,5 @@ import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; +import { RecordTableCellArrowKeysEffect } from '@/object-record/record-table/record-table-cell/components/RecordTableCellArrowKeysEffect'; import { RecordTableCellEditModePortal } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditModePortal'; import { RecordTableCellHoveredPortal } from '@/object-record/record-table/record-table-cell/components/RecordTableCellHoveredPortal'; import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState'; @@ -16,7 +17,12 @@ export const RecordTableCellPortals = () => { <> - {isRecordTableFocusActive && } + {isRecordTableFocusActive && ( + <> + + + + )} ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useMoveHoverToCurrentCell.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useMoveHoverToCurrentCell.test.tsx index d804584c3..57e42fcd6 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useMoveHoverToCurrentCell.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useMoveHoverToCurrentCell.test.tsx @@ -2,6 +2,7 @@ import { renderHook } from '@testing-library/react'; import { act } from 'react'; import { RecoilRoot, useRecoilValue } from 'recoil'; +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext'; @@ -13,14 +14,13 @@ import { } from '@/object-record/record-table/record-table-cell/hooks/__mocks__/cell'; import { useMoveHoverToCurrentCell } from '@/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell'; import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState'; const Wrapper = ({ children }: { children: React.ReactNode }) => ( { set(currentHotkeyScopeState, { - scope: TableHotkeyScope.TableFocus, + scope: RecordIndexHotkeyScope.RecordIndex, customScopes: {}, }); }} diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useSetIsRecordTableFocusActive.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useSetIsRecordTableFocusActive.test.tsx index 329049d7d..4f3194b75 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useSetIsRecordTableFocusActive.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useSetIsRecordTableFocusActive.test.tsx @@ -3,7 +3,7 @@ import React, { act } from 'react'; import { RecoilRoot, useRecoilValue } from 'recoil'; import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; -import { useSetIsRecordTableFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive'; +import { useSetIsRecordTableCellFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableCellFocusActive'; import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState'; import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState'; import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition'; @@ -47,8 +47,8 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => ( const renderHooks = () => { const { result } = renderHook( () => { - const { setIsFocusActive, setIsFocusActiveForCurrentPosition } = - useSetIsRecordTableFocusActive('test-table-id'); + const { setIsRecordTableCellFocusActive } = + useSetIsRecordTableCellFocusActive('test-table-id'); const isRecordTableFocusActive = useRecoilValue( isRecordTableCellFocusActiveComponentState.atomFamily({ instanceId: 'test-table-id', @@ -60,8 +60,7 @@ const renderHooks = () => { }), ); return { - setIsFocusActive, - setIsFocusActiveForCurrentPosition, + setIsRecordTableCellFocusActive, isRecordTableFocusActive, focusPosition, }; @@ -87,7 +86,10 @@ describe('useSetIsRecordTableFocusActive', () => { const cellPosition: TableCellPosition = { column: 1, row: 0 }; act(() => { - result.current.setIsFocusActive(true, cellPosition); + result.current.setIsRecordTableCellFocusActive({ + isRecordTableFocusActive: true, + cellPosition, + }); }); expect(mockGetElementById).toHaveBeenCalledWith('record-table-cell-1-0'); @@ -105,7 +107,10 @@ describe('useSetIsRecordTableFocusActive', () => { const cellPosition: TableCellPosition = { column: 1, row: 0 }; act(() => { - result.current.setIsFocusActive(false, cellPosition); + result.current.setIsRecordTableCellFocusActive({ + isRecordTableFocusActive: false, + cellPosition, + }); }); expect(mockGetElementById).toHaveBeenCalledWith('record-table-cell-1-0'); @@ -117,22 +122,6 @@ describe('useSetIsRecordTableFocusActive', () => { expect(result.current.focusPosition).toEqual(cellPosition); }); - it('should set focus for current position', () => { - const { result } = renderHooks(); - - act(() => { - result.current.setIsFocusActiveForCurrentPosition(true); - }); - - expect(mockGetElementById).toHaveBeenCalledWith('record-table-cell-1-0'); - - expect(mockClassList.add).toHaveBeenCalledWith('focus-active'); - - expect(result.current.isRecordTableFocusActive).toBe(true); - - expect(result.current.focusPosition).toEqual({ column: 1, row: 0 }); - }); - it('should handle case when the cell element is not found', () => { mockGetElementById.mockReturnValue(null); @@ -141,7 +130,10 @@ describe('useSetIsRecordTableFocusActive', () => { const cellPosition: TableCellPosition = { column: 1, row: 0 }; act(() => { - result.current.setIsFocusActive(true, cellPosition); + result.current.setIsRecordTableCellFocusActive({ + isRecordTableFocusActive: true, + cellPosition, + }); }); expect(mockGetElementById).toHaveBeenCalledWith('record-table-cell-1-0'); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx index 50a00e67b..00b1534be 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx @@ -21,12 +21,6 @@ import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -const setHotkeyScope = jest.fn(); - -jest.mock('@/ui/utilities/hotkey/hooks/useSetHotkeyScope', () => ({ - useSetHotkeyScope: () => setHotkeyScope, -})); - const onColumnsChange = jest.fn(); const recordTableId = 'scopeId'; @@ -94,10 +88,5 @@ describe('useCloseRecordTableCellInGroup', () => { expect(result.current.isDragSelectionStartEnabled()).toBe(true); expect(result.current.currentTableCellInEditModePosition).toBe(null); - expect(setHotkeyScope).toHaveBeenCalledWith('table-focus', { - goto: true, - keyboardShortcutMenu: true, - searchRecords: true, - }); }); }); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx index dfce054b7..9ff4969ab 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx @@ -21,12 +21,6 @@ import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -const setHotkeyScope = jest.fn(); - -jest.mock('@/ui/utilities/hotkey/hooks/useSetHotkeyScope', () => ({ - useSetHotkeyScope: () => setHotkeyScope, -})); - const onColumnsChange = jest.fn(); const recordTableId = 'scopeId'; @@ -95,10 +89,5 @@ describe('useCloseRecordTableCellNoGroup', () => { expect(result.current.isDragSelectionStartEnabled()).toBe(true); expect(result.current.currentTableCellInEditModePosition).toBe(null); - expect(setHotkeyScope).toHaveBeenCalledWith('table-focus', { - goto: true, - keyboardShortcutMenu: true, - searchRecords: true, - }); }); }); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup.ts index 14c309e6a..6e8bd28d8 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup.ts @@ -1,19 +1,18 @@ import { useRecoilCallback } from 'recoil'; import { FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/FocusClickOutsideListenerId'; +import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId'; import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; -import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; -import { useResetFocusStack } from '@/ui/utilities/focus/hooks/useResetFocusStack'; +import { clickOutsideListenerIsActivatedComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerIsActivatedComponentState'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; export const useCloseRecordTableCellInGroup = () => { const { recordTableId } = useRecordTableContextOrThrow(); - const setHotkeyScope = useSetHotkeyScope(); const { setDragSelectionStartEnabled } = useDragSelect(); const { toggleClickOutside } = useClickOutsideListener( @@ -23,28 +22,24 @@ export const useCloseRecordTableCellInGroup = () => { const closeCurrentTableCellInEditMode = useCloseCurrentTableCellInEditMode(recordTableId); - const { resetFocusStack } = useResetFocusStack(); + const clickOutsideListenerIsActivatedState = + useRecoilComponentCallbackStateV2( + clickOutsideListenerIsActivatedComponentState, + RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID, + ); const closeTableCellInGroup = useRecoilCallback( - () => () => { - toggleClickOutside(true); - setDragSelectionStartEnabled(true); - closeCurrentTableCellInEditMode(); - - // TODO: Remove this once we've fully migrated away from hotkey scopes - resetFocusStack(); - - setHotkeyScope(TableHotkeyScope.TableFocus, { - goto: true, - keyboardShortcutMenu: true, - searchRecords: true, - }); - }, + ({ set }) => + () => { + toggleClickOutside(true); + setDragSelectionStartEnabled(true); + closeCurrentTableCellInEditMode(); + set(clickOutsideListenerIsActivatedState, true); + }, [ + clickOutsideListenerIsActivatedState, closeCurrentTableCellInEditMode, - resetFocusStack, setDragSelectionStartEnabled, - setHotkeyScope, toggleClickOutside, ], ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup.ts index 3ab03ab6e..8d67d4d79 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup.ts @@ -1,19 +1,17 @@ import { FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/FocusClickOutsideListenerId'; import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; -import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; +import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; -import { useResetFocusStack } from '@/ui/utilities/focus/hooks/useResetFocusStack'; -import { useCallback } from 'react'; +import { clickOutsideListenerIsActivatedComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerIsActivatedComponentState'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; +import { useRecoilCallback } from 'recoil'; export const useCloseRecordTableCellNoGroup = () => { const { recordTableId } = useRecordTableContextOrThrow(); - const setHotkeyScope = useSetHotkeyScope(); - const { setDragSelectionStartEnabled } = useDragSelect(); const { toggleClickOutside } = useClickOutsideListener( @@ -23,28 +21,27 @@ export const useCloseRecordTableCellNoGroup = () => { const closeCurrentTableCellInEditMode = useCloseCurrentTableCellInEditMode(recordTableId); - const { resetFocusStack } = useResetFocusStack(); + const clickOutsideListenerIsActivatedState = + useRecoilComponentCallbackStateV2( + clickOutsideListenerIsActivatedComponentState, + RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID, + ); - const closeTableCellNoGroup = useCallback(() => { - toggleClickOutside(true); - setDragSelectionStartEnabled(true); - closeCurrentTableCellInEditMode(); - - // TODO: Remove this once we've fully migrated away from hotkey scopes - resetFocusStack(); - - setHotkeyScope(TableHotkeyScope.TableFocus, { - goto: true, - keyboardShortcutMenu: true, - searchRecords: true, - }); - }, [ - closeCurrentTableCellInEditMode, - resetFocusStack, - setDragSelectionStartEnabled, - setHotkeyScope, - toggleClickOutside, - ]); + const closeTableCellNoGroup = useRecoilCallback( + ({ set }) => + () => { + toggleClickOutside(true); + setDragSelectionStartEnabled(true); + closeCurrentTableCellInEditMode(); + set(clickOutsideListenerIsActivatedState, true); + }, + [ + clickOutsideListenerIsActivatedState, + closeCurrentTableCellInEditMode, + setDragSelectionStartEnabled, + toggleClickOutside, + ], + ); return { closeTableCellNoGroup, diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useCurrentlyFocusedRecordTableCellFocusId.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useCurrentlyFocusedRecordTableCellFocusId.ts new file mode 100644 index 000000000..1e798fefe --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useCurrentlyFocusedRecordTableCellFocusId.ts @@ -0,0 +1,18 @@ +import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; +import { getRecordTableCellFocusId } from '@/object-record/record-table/record-table-cell/utils/getRecordTableCellFocusId'; +import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; + +export const useCurrentlyFocusedRecordTableCellFocusId = () => { + const { recordTableId } = useRecordTableContextOrThrow(); + + const focusPosition = useRecoilComponentValueV2( + recordTableFocusPositionComponentState, + recordTableId, + ); + + return getRecordTableCellFocusId({ + recordTableId, + cellPosition: focusPosition, + }); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useFocusRecordTableCell.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useFocusRecordTableCell.ts new file mode 100644 index 000000000..543cb5f88 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useFocusRecordTableCell.ts @@ -0,0 +1,88 @@ +import { useRecoilCallback } from 'recoil'; + +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; +import { getRecordTableCellFocusId } from '@/object-record/record-table/record-table-cell/utils/getRecordTableCellFocusId'; +import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; +import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState'; +import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; +import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; +import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; +import { TableCellPosition } from '../../types/TableCellPosition'; +import { useSetIsRecordTableCellFocusActive } from './useSetIsRecordTableCellFocusActive'; + +export const useFocusRecordTableCell = (recordTableId?: string) => { + const recordTableIdFromProps = useAvailableComponentInstanceIdOrThrow( + RecordTableComponentInstanceContext, + recordTableId, + ); + + const focusPositionState = useRecoilComponentCallbackStateV2( + recordTableFocusPositionComponentState, + recordTableIdFromProps, + ); + + const { setIsRecordTableCellFocusActive } = + useSetIsRecordTableCellFocusActive(recordTableIdFromProps); + + const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); + const { removeFocusItemFromFocusStackById } = + useRemoveFocusItemFromFocusStackById(); + + const focusRecordTableCell = useRecoilCallback( + ({ set, snapshot }) => { + return (newPosition: TableCellPosition) => { + const currentPosition = snapshot + .getLoadable(focusPositionState) + .getValue(); + + const currentCellFocusId = getRecordTableCellFocusId({ + recordTableId: recordTableIdFromProps, + cellPosition: currentPosition, + }); + + removeFocusItemFromFocusStackById({ + focusId: currentCellFocusId, + }); + + set(focusPositionState, newPosition); + + setIsRecordTableCellFocusActive({ + isRecordTableFocusActive: false, + cellPosition: currentPosition, + }); + setIsRecordTableCellFocusActive({ + isRecordTableFocusActive: true, + cellPosition: newPosition, + }); + + const cellFocusId = getRecordTableCellFocusId({ + recordTableId: recordTableIdFromProps, + cellPosition: newPosition, + }); + + pushFocusItemToFocusStack({ + focusId: cellFocusId, + component: { + type: FocusComponentType.RECORD_TABLE_CELL, + instanceId: cellFocusId, + }, + hotkeyScope: { + scope: RecordIndexHotkeyScope.RecordIndex, + }, + memoizeKey: cellFocusId, + }); + }; + }, + [ + focusPositionState, + recordTableIdFromProps, + removeFocusItemFromFocusStackById, + setIsRecordTableCellFocusActive, + pushFocusItemToFocusStack, + ], + ); + + return { focusRecordTableCell }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell.ts index a8e4f1e2e..459dfefa5 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell.ts @@ -37,7 +37,6 @@ export const useMoveHoverToCurrentCell = (recordTableId: string) => { ); if ( - currentHotkeyScope.scope !== TableHotkeyScope.TableFocus && currentHotkeyScope.scope !== TableHotkeyScope.CellEditMode && currentHotkeyScope.scope !== AppHotkeyScope.CommandMenuOpen && currentHotkeyScope.scope !== RecordIndexHotkeyScope.RecordIndex diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts index a24729c4c..9df49aacb 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts @@ -24,9 +24,9 @@ import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInput import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView'; -import { useSetRecordTableFocusPosition } from '@/object-record/record-table/hooks/internal/useSetRecordTableFocusPosition'; import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow'; import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; +import { useFocusRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useFocusRecordTableCell'; import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState'; import { clickOutsideListenerIsActivatedComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerIsActivatedComponentState'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; @@ -90,7 +90,7 @@ export const useOpenRecordTableCellV2 = (recordTableId: string) => { recordTableId, ); - const setFocusPosition = useSetRecordTableFocusPosition(); + const { focusRecordTableCell } = useFocusRecordTableCell(); const { openRecordFromIndexView } = useOpenRecordFromIndexView(); @@ -157,7 +157,7 @@ export const useOpenRecordTableCellV2 = (recordTableId: string) => { deactivateRecordTableRow(); - setFocusPosition(cellPosition); + focusRecordTableCell(cellPosition); setIsRowFocusActive(false); @@ -194,7 +194,7 @@ export const useOpenRecordTableCellV2 = (recordTableId: string) => { [ clickOutsideListenerIsActivatedState, deactivateRecordTableRow, - setFocusPosition, + focusRecordTableCell, setIsRowFocusActive, setDragSelectionStartEnabled, openFieldInput, diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useRecordTableCellFocusHotkeys.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useRecordTableCellFocusHotkeys.ts new file mode 100644 index 000000000..0be19ccc9 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useRecordTableCellFocusHotkeys.ts @@ -0,0 +1,56 @@ +import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; +import { useRecordTableMoveFocusedCell } from '@/object-record/record-table/hooks/useRecordTableMoveFocusedCell'; +import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement'; +import { Key } from 'ts-key-enum'; + +export const useRecordTableCellFocusHotkeys = ({ + focusId, + hotkeyScope, +}: { + focusId: string; + hotkeyScope: string; +}) => { + const { recordTableId } = useRecordTableContextOrThrow(); + + const { moveFocus } = useRecordTableMoveFocusedCell(recordTableId); + + useHotkeysOnFocusedElement({ + keys: [Key.ArrowUp], + callback: () => { + moveFocus('up'); + }, + focusId, + scope: hotkeyScope, + dependencies: [moveFocus], + }); + + useHotkeysOnFocusedElement({ + keys: [Key.ArrowDown], + callback: () => { + moveFocus('down'); + }, + focusId, + scope: hotkeyScope, + dependencies: [moveFocus], + }); + + useHotkeysOnFocusedElement({ + keys: [Key.ArrowLeft], + callback: () => { + moveFocus('left'); + }, + focusId, + scope: hotkeyScope, + dependencies: [moveFocus], + }); + + useHotkeysOnFocusedElement({ + keys: [Key.ArrowRight], + callback: () => { + moveFocus('right'); + }, + focusId, + scope: hotkeyScope, + dependencies: [moveFocus], + }); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableCellFocusActive.ts similarity index 53% rename from packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive.ts rename to packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableCellFocusActive.ts index 8cf482847..2ee7d7e6a 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableCellFocusActive.ts @@ -1,23 +1,23 @@ import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState'; -import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState'; import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilCallback } from 'recoil'; -export const useSetIsRecordTableFocusActive = (recordTableId?: string) => { +export const useSetIsRecordTableCellFocusActive = (recordTableId?: string) => { const isRecordTableFocusActiveState = useRecoilComponentCallbackStateV2( isRecordTableCellFocusActiveComponentState, recordTableId, ); - const focusPositionState = useRecoilComponentCallbackStateV2( - recordTableFocusPositionComponentState, - recordTableId, - ); - - const setIsFocusActive = useRecoilCallback( + const setIsRecordTableCellFocusActive = useRecoilCallback( ({ set }) => - (isRecordTableFocusActive: boolean, cellPosition: TableCellPosition) => { + ({ + isRecordTableFocusActive, + cellPosition, + }: { + isRecordTableFocusActive: boolean; + cellPosition: TableCellPosition; + }) => { const cellId = `record-table-cell-${cellPosition.column}-${cellPosition.row}`; const cellElement = document.getElementById(cellId); @@ -35,17 +35,7 @@ export const useSetIsRecordTableFocusActive = (recordTableId?: string) => { [isRecordTableFocusActiveState], ); - const setIsFocusActiveForCurrentPosition = useRecoilCallback( - ({ snapshot }) => - (isRecordTableFocusActive: boolean) => { - const currentPosition = snapshot - .getLoadable(focusPositionState) - .getValue(); - - setIsFocusActive(isRecordTableFocusActive, currentPosition); - }, - [setIsFocusActive, focusPositionState], - ); - - return { setIsFocusActive, setIsFocusActiveForCurrentPosition }; + return { + setIsRecordTableCellFocusActive, + }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useUnfocusRecordTableCell.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useUnfocusRecordTableCell.ts new file mode 100644 index 000000000..5110c8545 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useUnfocusRecordTableCell.ts @@ -0,0 +1,59 @@ +import { useRecoilCallback } from 'recoil'; + +import { getRecordTableCellFocusId } from '@/object-record/record-table/record-table-cell/utils/getRecordTableCellFocusId'; +import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; +import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState'; +import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; +import { useSetIsRecordTableCellFocusActive } from './useSetIsRecordTableCellFocusActive'; + +export const useUnfocusRecordTableCell = (recordTableId?: string) => { + const recordTableIdFromProps = useAvailableComponentInstanceIdOrThrow( + RecordTableComponentInstanceContext, + recordTableId, + ); + + const focusPositionState = useRecoilComponentCallbackStateV2( + recordTableFocusPositionComponentState, + recordTableIdFromProps, + ); + + const { setIsRecordTableCellFocusActive } = + useSetIsRecordTableCellFocusActive(recordTableIdFromProps); + + const { removeFocusItemFromFocusStackById } = + useRemoveFocusItemFromFocusStackById(); + + const unfocusRecordTableCell = useRecoilCallback( + ({ snapshot }) => { + return () => { + const currentPosition = snapshot + .getLoadable(focusPositionState) + .getValue(); + + const currentCellFocusId = getRecordTableCellFocusId({ + recordTableId: recordTableIdFromProps, + cellPosition: currentPosition, + }); + + removeFocusItemFromFocusStackById({ + focusId: currentCellFocusId, + }); + + setIsRecordTableCellFocusActive({ + isRecordTableFocusActive: false, + cellPosition: currentPosition, + }); + }; + }, + [ + focusPositionState, + recordTableIdFromProps, + removeFocusItemFromFocusStackById, + setIsRecordTableCellFocusActive, + ], + ); + + return { unfocusRecordTableCell }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/utils/getRecordTableCellFocusId.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/utils/getRecordTableCellFocusId.ts new file mode 100644 index 000000000..564daa0a1 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/utils/getRecordTableCellFocusId.ts @@ -0,0 +1,11 @@ +import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition'; + +export const getRecordTableCellFocusId = ({ + recordTableId, + cellPosition, +}: { + recordTableId: string; + cellPosition: TableCellPosition; +}) => { + return `${recordTableId}-cell-${cellPosition.row}-${cellPosition.column}`; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/hooks/useOpenRecordFilterChipFromTableHeader.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/hooks/useOpenRecordFilterChipFromTableHeader.ts index c2ccc2766..061fee4d1 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/hooks/useOpenRecordFilterChipFromTableHeader.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/hooks/useOpenRecordFilterChipFromTableHeader.ts @@ -2,7 +2,7 @@ import { useCreateEmptyRecordFilterFromFieldMetadataItem } from '@/object-record import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext'; import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; -import { useOpenDropdownFromOutside } from '@/ui/layout/dropdown/hooks/useOpenDropdownFromOutside'; +import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetEditableFilterChipDropdownStates } from '@/views/hooks/useSetEditableFilterChipDropdownStates'; import { isDefined } from 'twenty-shared/utils'; @@ -20,7 +20,7 @@ export const useOpenRecordFilterChipFromTableHeader = () => { const { upsertRecordFilter } = useUpsertRecordFilter(); - const { openDropdownFromOutside } = useOpenDropdownFromOutside(); + const { openDropdown } = useDropdownV2(); const { setEditableFilterChipDropdownStates } = useSetEditableFilterChipDropdownStates(); @@ -45,7 +45,7 @@ export const useOpenRecordFilterChipFromTableHeader = () => { if (isDefined(existingNonAdvancedRecordFilter)) { setEditableFilterChipDropdownStates(existingNonAdvancedRecordFilter); - openDropdownFromOutside(existingNonAdvancedRecordFilter.id); + openDropdown(existingNonAdvancedRecordFilter.id); return; } @@ -56,7 +56,7 @@ export const useOpenRecordFilterChipFromTableHeader = () => { upsertRecordFilter(newRecordFilter); setEditableFilterChipDropdownStates(newRecordFilter); - openDropdownFromOutside(newRecordFilter.id); + openDropdown(newRecordFilter.id); }; return { openRecordFilterChipFromTableHeader }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRow.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRow.tsx index f3b4cfe61..7f44840d7 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRow.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRow.tsx @@ -4,6 +4,7 @@ import { RecordTableCellGrip } from '@/object-record/record-table/record-table-c import { RecordTableLastEmptyCell } from '@/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell'; import { RecordTableCells } from '@/object-record/record-table/record-table-row/components/RecordTableCells'; import { RecordTableDraggableTr } from '@/object-record/record-table/record-table-row/components/RecordTableDraggableTr'; +import { RecordTableRowArrowKeysEffect } from '@/object-record/record-table/record-table-row/components/RecordTableRowArrowKeysEffect'; import { RecordTableRowHotkeyEffect } from '@/object-record/record-table/record-table-row/components/RecordTableRowHotkeyEffect'; import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState'; import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState'; @@ -41,7 +42,12 @@ export const RecordTableRow = ({ draggableIndex={rowIndexForDrag} focusIndex={rowIndexForFocus} > - {isRowFocusActive && isFocused && } + {isRowFocusActive && isFocused && ( + <> + + + + )} diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowArrowKeysEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowArrowKeysEffect.tsx new file mode 100644 index 000000000..076ad4fbb --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowArrowKeysEffect.tsx @@ -0,0 +1,14 @@ +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; +import { useRecordTableRowFocusHotkeys } from '@/object-record/record-table/hooks/useRecordTableRowFocusHotkeys'; +import { useRecordTableRowFocusId } from '@/object-record/record-table/record-table-row/hooks/useRecordTableRowFocusId'; + +export const RecordTableRowArrowKeysEffect = () => { + const recordTableRowFocusId = useRecordTableRowFocusId(); + + useRecordTableRowFocusHotkeys({ + focusId: recordTableRowFocusId, + hotkeyScope: RecordIndexHotkeyScope.RecordIndex, + }); + + return null; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowHotkeyEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowHotkeyEffect.tsx index 763d7d18c..c317a34e2 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowHotkeyEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowHotkeyEffect.tsx @@ -1,76 +1,10 @@ -import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu'; -import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext'; -import { useSetRecordTableFocusPosition } from '@/object-record/record-table/hooks/internal/useSetRecordTableFocusPosition'; -import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow'; -import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected'; -import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; -import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { Key } from 'ts-key-enum'; +import { useRecordTableRowFocusId } from '@/object-record/record-table/record-table-row/hooks/useRecordTableRowFocusId'; +import { useRecordTableRowHotkeys } from '@/object-record/record-table/record-table-row/hooks/useRecordTableRowHotkeys'; export const RecordTableRowHotkeyEffect = () => { - const { isSelected, recordId, objectNameSingular, rowIndex } = - useRecordTableRowContextOrThrow(); + const recordTableRowFocusId = useRecordTableRowFocusId(); - const { setCurrentRowSelected } = useSetCurrentRowSelected(); - - const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); - - const { activateRecordTableRow } = useActiveRecordTableRow(); - - const setIsRowFocusActive = useSetRecoilComponentStateV2( - isRecordTableRowFocusActiveComponentState, - ); - - const setFocusPosition = useSetRecordTableFocusPosition(); - - useScopedHotkeys( - 'x', - () => { - setCurrentRowSelected({ - newSelectedState: !isSelected, - }); - }, - TableHotkeyScope.TableFocus, - ); - - useScopedHotkeys( - `${Key.Shift}+x`, - () => { - setCurrentRowSelected({ - newSelectedState: !isSelected, - shouldSelectRange: true, - }); - }, - TableHotkeyScope.TableFocus, - ); - - useScopedHotkeys( - [`${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`], - () => { - openRecordInCommandMenu({ - recordId: recordId, - objectNameSingular: objectNameSingular, - isNewRecord: false, - }); - - activateRecordTableRow(rowIndex); - }, - TableHotkeyScope.TableFocus, - ); - - useScopedHotkeys( - Key.Enter, - () => { - setIsRowFocusActive(false); - setFocusPosition({ - row: rowIndex, - column: 0, - }); - }, - TableHotkeyScope.TableFocus, - ); + useRecordTableRowHotkeys(recordTableRowFocusId); return null; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/hooks/useRecordTableRowFocusId.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/hooks/useRecordTableRowFocusId.ts new file mode 100644 index 000000000..2e242064a --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/hooks/useRecordTableRowFocusId.ts @@ -0,0 +1,14 @@ +import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; +import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext'; +import { getRecordTableRowFocusId } from '@/object-record/record-table/record-table-row/utils/getRecordTableRowFocusId'; + +export const useRecordTableRowFocusId = () => { + const { rowIndex } = useRecordTableRowContextOrThrow(); + + const { recordTableId } = useRecordTableContextOrThrow(); + + return getRecordTableRowFocusId({ + recordTableId, + rowIndex, + }); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/hooks/useRecordTableRowHotkeys.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/hooks/useRecordTableRowHotkeys.ts new file mode 100644 index 000000000..74fc9b35b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/hooks/useRecordTableRowHotkeys.ts @@ -0,0 +1,145 @@ +import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu'; +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; +import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; +import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext'; +import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow'; +import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; +import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; +import { useFocusRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useFocusRecordTableCell'; +import { getRecordTableCellFocusId } from '@/object-record/record-table/record-table-cell/utils/getRecordTableCellFocusId'; +import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected'; +import { isAtLeastOneTableRowSelectedSelector } from '@/object-record/record-table/record-table-row/states/isAtLeastOneTableRowSelectedSelector'; +import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState'; +import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; +import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; +import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import { Key } from 'ts-key-enum'; + +export const useRecordTableRowHotkeys = (focusId: string) => { + const { isSelected, recordId, objectNameSingular, rowIndex } = + useRecordTableRowContextOrThrow(); + + const { setCurrentRowSelected } = useSetCurrentRowSelected(); + + const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); + + const { activateRecordTableRow } = useActiveRecordTableRow(); + + const setIsRowFocusActive = useSetRecoilComponentStateV2( + isRecordTableRowFocusActiveComponentState, + ); + + const { focusRecordTableCell } = useFocusRecordTableCell(); + + const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); + + const { recordTableId } = useRecordTableContextOrThrow(); + + const handleSelectRow = () => { + setCurrentRowSelected({ + newSelectedState: !isSelected, + }); + }; + + const handleSelectRowWithShift = () => { + setCurrentRowSelected({ + newSelectedState: !isSelected, + shouldSelectRange: true, + }); + }; + + const handleOpenRecordInCommandMenu = () => { + openRecordInCommandMenu({ + recordId: recordId, + objectNameSingular: objectNameSingular, + isNewRecord: false, + }); + + activateRecordTableRow(rowIndex); + }; + + const handleEnterRow = () => { + setIsRowFocusActive(false); + const cellPosition = { + row: rowIndex, + column: 0, + }; + focusRecordTableCell(cellPosition); + + const cellFocusId = getRecordTableCellFocusId({ + recordTableId, + cellPosition, + }); + + pushFocusItemToFocusStack({ + focusId: cellFocusId, + component: { + type: FocusComponentType.RECORD_TABLE_CELL, + instanceId: cellFocusId, + }, + hotkeyScope: { + scope: RecordIndexHotkeyScope.RecordIndex, + }, + memoizeKey: cellFocusId, + }); + }; + + const { resetTableRowSelection } = useRecordTable({ + recordTableId, + }); + + const { unfocusRecordTableRow } = useFocusedRecordTableRow(recordTableId); + + const isAtLeastOneRecordSelected = useRecoilComponentValueV2( + isAtLeastOneTableRowSelectedSelector, + ); + + const handleEscape = () => { + unfocusRecordTableRow(); + if (isAtLeastOneRecordSelected) { + resetTableRowSelection(); + } + }; + + useHotkeysOnFocusedElement({ + keys: ['x'], + callback: handleSelectRow, + focusId, + scope: RecordIndexHotkeyScope.RecordIndex, + dependencies: [handleSelectRow], + }); + + useHotkeysOnFocusedElement({ + keys: [`${Key.Shift}+x`], + callback: handleSelectRowWithShift, + focusId, + scope: RecordIndexHotkeyScope.RecordIndex, + dependencies: [handleSelectRowWithShift], + }); + + useHotkeysOnFocusedElement({ + keys: [`${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`], + callback: handleOpenRecordInCommandMenu, + focusId, + scope: RecordIndexHotkeyScope.RecordIndex, + dependencies: [handleOpenRecordInCommandMenu], + }); + + useHotkeysOnFocusedElement({ + keys: [Key.Enter], + callback: handleEnterRow, + focusId, + scope: RecordIndexHotkeyScope.RecordIndex, + dependencies: [handleEnterRow], + }); + + useHotkeysOnFocusedElement({ + keys: [Key.Escape], + callback: handleEscape, + focusId, + scope: RecordIndexHotkeyScope.RecordIndex, + dependencies: [handleEscape], + }); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/utils/getRecordTableRowFocusId.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/utils/getRecordTableRowFocusId.ts new file mode 100644 index 000000000..be0310b46 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/utils/getRecordTableRowFocusId.ts @@ -0,0 +1,9 @@ +export const getRecordTableRowFocusId = ({ + recordTableId, + rowIndex, +}: { + recordTableId: string; + rowIndex: number; +}) => { + return `${recordTableId}-row-${rowIndex}`; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/types/TableHotkeyScope.ts b/packages/twenty-front/src/modules/object-record/record-table/types/TableHotkeyScope.ts index ad2fe85eb..3695bf17e 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/types/TableHotkeyScope.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/types/TableHotkeyScope.ts @@ -1,6 +1,3 @@ export enum TableHotkeyScope { - CellDoubleTextInput = 'cell-double-text-input', CellEditMode = 'cell-edit-mode', - CellDateEditMode = 'cell-date-edit-mode', - TableFocus = 'table-focus', } diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/Dropdown.stories.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/Dropdown.stories.tsx index e5cbb3273..16b2753a6 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/Dropdown.stories.tsx +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/Dropdown.stories.tsx @@ -395,6 +395,7 @@ const initializeModalState = ({ set }: { set: SetRecoilState }) => { enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysConflictingWithKeyboard: true, }, + memoizeKey: 'global', }, ]); }; diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown.ts b/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown.ts index dc58ef07f..11b417aa3 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown.ts +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown.ts @@ -1,14 +1,15 @@ import { useCloseDropdownFromOutside } from '@/ui/layout/dropdown/hooks/useCloseDropdownFromOutside'; import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState'; import { previousDropdownFocusIdState } from '@/ui/layout/dropdown/states/previousDropdownFocusIdState'; -import { useRemoveFocusItemFromFocusStack } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStack'; +import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const useCloseAnyOpenDropdown = () => { const { closeDropdownFromOutside } = useCloseDropdownFromOutside(); - const { removeFocusItemFromFocusStack } = useRemoveFocusItemFromFocusStack(); + const { removeFocusItemFromFocusStackById } = + useRemoveFocusItemFromFocusStackById(); const closeAnyOpenDropdown = useRecoilCallback( ({ snapshot, set }) => @@ -33,24 +34,22 @@ export const useCloseAnyOpenDropdown = () => { if (isDefined(activeDropdownFocusId)) { closeDropdownFromOutside(activeDropdownFocusId); - removeFocusItemFromFocusStack({ + removeFocusItemFromFocusStackById({ focusId: activeDropdownFocusId, - memoizeKey: 'global', }); } if (thereIsOneNestedDropdownOpen) { closeDropdownFromOutside(previousDropdownFocusId); - removeFocusItemFromFocusStack({ + removeFocusItemFromFocusStackById({ focusId: previousDropdownFocusId, - memoizeKey: 'global', }); } set(previousDropdownFocusIdState, null); set(activeDropdownFocusIdState, null); }, - [closeDropdownFromOutside, removeFocusItemFromFocusStack], + [closeDropdownFromOutside, removeFocusItemFromFocusStackById], ); return { closeAnyOpenDropdown }; diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdown.ts b/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdown.ts index 31d9ff9d9..d04afd915 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdown.ts +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdown.ts @@ -4,7 +4,7 @@ import { useDropdownStates } from '@/ui/layout/dropdown/hooks/internal/useDropdo import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId'; import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; -import { useRemoveFocusItemFromFocusStack } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStack'; +import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; import { GlobalHotkeysConfig } from '@/ui/utilities/hotkey/types/GlobalHotkeysConfig'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; @@ -12,7 +12,8 @@ import { useCallback } from 'react'; export const useDropdown = (dropdownId?: string) => { const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); - const { removeFocusItemFromFocusStack } = useRemoveFocusItemFromFocusStack(); + const { removeFocusItemFromFocusStackById } = + useRemoveFocusItemFromFocusStackById(); const { scopeId, isDropdownOpenState, dropdownPlacementState } = useDropdownStates({ dropdownScopeId: dropdownId }); @@ -34,16 +35,15 @@ export const useDropdown = (dropdownId?: string) => { if (isDropdownOpen) { setIsDropdownOpen(false); goBackToPreviousDropdownFocusId(); - removeFocusItemFromFocusStack({ + removeFocusItemFromFocusStackById({ focusId: dropdownId ?? scopeId, - memoizeKey: 'global', }); } }, [ isDropdownOpen, setIsDropdownOpen, goBackToPreviousDropdownFocusId, - removeFocusItemFromFocusStack, + removeFocusItemFromFocusStackById, dropdownId, scopeId, ]); @@ -63,7 +63,6 @@ export const useDropdown = (dropdownId?: string) => { globalHotkeysConfig, // TODO: Remove this once we've fully migrated away from hotkey scopes hotkeyScope: { scope: 'dropdown' } as HotkeyScope, - memoizeKey: 'global', }); } }, diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdownV2.ts b/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdownV2.ts index c594e4dd6..8ba1fdd34 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdownV2.ts +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useDropdownV2.ts @@ -4,7 +4,7 @@ import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/u import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; -import { useRemoveFocusItemFromFocusStack } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStack'; +import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; import { GlobalHotkeysConfig } from '@/ui/utilities/hotkey/types/GlobalHotkeysConfig'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; @@ -15,7 +15,8 @@ export const useDropdownV2 = () => { const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); - const { removeFocusItemFromFocusStack } = useRemoveFocusItemFromFocusStack(); + const { removeFocusItemFromFocusStackById } = + useRemoveFocusItemFromFocusStackById(); const { setActiveDropdownFocusIdAndMemorizePrevious } = useSetActiveDropdownFocusIdAndMemorizePrevious(); @@ -30,9 +31,8 @@ export const useDropdownV2 = () => { .getValue(); if (isDropdownOpen) { - removeFocusItemFromFocusStack({ + removeFocusItemFromFocusStackById({ focusId: scopeId, - memoizeKey: 'global', }); goBackToPreviousDropdownFocusId(); set( @@ -43,7 +43,7 @@ export const useDropdownV2 = () => { ); } }, - [removeFocusItemFromFocusStack, goBackToPreviousDropdownFocusId], + [removeFocusItemFromFocusStackById, goBackToPreviousDropdownFocusId], ); const openDropdown = useRecoilCallback( diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useOpenDropdownFromOutside.ts b/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useOpenDropdownFromOutside.ts deleted file mode 100644 index 7e1c98ab1..000000000 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useOpenDropdownFromOutside.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; -import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; -import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; -import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; -import { useRecoilCallback } from 'recoil'; - -export const useOpenDropdownFromOutside = () => { - const { setActiveDropdownFocusIdAndMemorizePrevious } = - useSetActiveDropdownFocusIdAndMemorizePrevious(); - - const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(); - - const openDropdownFromOutside = useRecoilCallback( - ({ set }) => { - return (dropdownId: string) => { - const dropdownOpenState = extractComponentState( - isDropdownOpenComponentState, - dropdownId, - ); - - setActiveDropdownFocusIdAndMemorizePrevious(dropdownId); - setHotkeyScopeAndMemorizePreviousScope({ - scope: dropdownId, - }); - - set(dropdownOpenState, true); - }; - }, - [ - setActiveDropdownFocusIdAndMemorizePrevious, - setHotkeyScopeAndMemorizePreviousScope, - ], - ); - - return { openDropdownFromOutside }; -}; diff --git a/packages/twenty-front/src/modules/ui/layout/modal/components/__stories__/ConfirmationModal.stories.tsx b/packages/twenty-front/src/modules/ui/layout/modal/components/__stories__/ConfirmationModal.stories.tsx index 3c9b9a09e..cfa5d94e5 100644 --- a/packages/twenty-front/src/modules/ui/layout/modal/components/__stories__/ConfirmationModal.stories.tsx +++ b/packages/twenty-front/src/modules/ui/layout/modal/components/__stories__/ConfirmationModal.stories.tsx @@ -44,6 +44,7 @@ const initializeState = ({ set }: { set: SetRecoilState }) => { enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysConflictingWithKeyboard: true, }, + memoizeKey: 'global', }, ]); }; diff --git a/packages/twenty-front/src/modules/ui/layout/modal/components/__stories__/Modal.stories.tsx b/packages/twenty-front/src/modules/ui/layout/modal/components/__stories__/Modal.stories.tsx index c4b9c9c25..68e5dd90a 100644 --- a/packages/twenty-front/src/modules/ui/layout/modal/components/__stories__/Modal.stories.tsx +++ b/packages/twenty-front/src/modules/ui/layout/modal/components/__stories__/Modal.stories.tsx @@ -44,6 +44,7 @@ const initializeState = ({ set }: { set: SetRecoilState }) => { enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysConflictingWithKeyboard: true, }, + memoizeKey: 'global', }, ]); }; diff --git a/packages/twenty-front/src/modules/ui/layout/modal/hooks/useModal.tsx b/packages/twenty-front/src/modules/ui/layout/modal/hooks/useModal.tsx index b766a9f77..342183078 100644 --- a/packages/twenty-front/src/modules/ui/layout/modal/hooks/useModal.tsx +++ b/packages/twenty-front/src/modules/ui/layout/modal/hooks/useModal.tsx @@ -1,13 +1,14 @@ import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope'; import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState'; import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; -import { useRemoveFocusItemFromFocusStack } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStack'; +import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; import { useRecoilCallback } from 'recoil'; export const useModal = () => { const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); - const { removeFocusItemFromFocusStack } = useRemoveFocusItemFromFocusStack(); + const { removeFocusItemFromFocusStackById } = + useRemoveFocusItemFromFocusStackById(); const closeModal = useRecoilCallback( ({ set, snapshot }) => @@ -22,9 +23,8 @@ export const useModal = () => { return; } - removeFocusItemFromFocusStack({ + removeFocusItemFromFocusStackById({ focusId: modalId, - memoizeKey: modalId, }); set( @@ -32,7 +32,7 @@ export const useModal = () => { false, ); }, - [removeFocusItemFromFocusStack], + [removeFocusItemFromFocusStackById], ); const openModal = useRecoilCallback( diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/hooks/useListenRightDrawerClose.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/hooks/useListenRightDrawerClose.ts deleted file mode 100644 index 29fc73d53..000000000 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/hooks/useListenRightDrawerClose.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { RIGHT_DRAWER_CLOSE_EVENT_NAME } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent'; -import { useEffect } from 'react'; - -export const useListenRightDrawerClose = (callback: () => void) => { - useEffect(() => { - window.addEventListener(RIGHT_DRAWER_CLOSE_EVENT_NAME, callback); - - return () => { - window.removeEventListener(RIGHT_DRAWER_CLOSE_EVENT_NAME, callback); - }; - }, [callback]); -}; diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/hooks/useListenToSidePanelClosing.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/hooks/useListenToSidePanelClosing.ts new file mode 100644 index 000000000..d3640744c --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/hooks/useListenToSidePanelClosing.ts @@ -0,0 +1,12 @@ +import { SIDE_PANEL_CLOSE_EVENT_NAME } from '@/ui/layout/right-drawer/utils/emitSidePanelCloseEvent'; +import { useEffect } from 'react'; + +export const useListenToSidePanelClosing = (callback: () => void) => { + useEffect(() => { + window.addEventListener(SIDE_PANEL_CLOSE_EVENT_NAME, callback); + + return () => { + window.removeEventListener(SIDE_PANEL_CLOSE_EVENT_NAME, callback); + }; + }, [callback]); +}; diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/hooks/useListenToSidePanelOpening.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/hooks/useListenToSidePanelOpening.ts new file mode 100644 index 000000000..724ffd639 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/hooks/useListenToSidePanelOpening.ts @@ -0,0 +1,12 @@ +import { SIDE_PANEL_OPEN_EVENT_NAME } from '@/ui/layout/right-drawer/utils/emitSidePanelOpenEvent'; +import { useEffect } from 'react'; + +export const useListenToSidePanelOpening = (callback: () => void) => { + useEffect(() => { + window.addEventListener(SIDE_PANEL_OPEN_EVENT_NAME, callback); + + return () => { + window.removeEventListener(SIDE_PANEL_OPEN_EVENT_NAME, callback); + }; + }, [callback]); +}; diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent.ts deleted file mode 100644 index e147a2f2f..000000000 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const RIGHT_DRAWER_CLOSE_EVENT_NAME = 'right-drawer-close'; - -export const emitRightDrawerCloseEvent = () => { - window.dispatchEvent(new CustomEvent(RIGHT_DRAWER_CLOSE_EVENT_NAME)); -}; diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/utils/emitSidePanelCloseEvent.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/utils/emitSidePanelCloseEvent.ts new file mode 100644 index 000000000..63003904e --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/utils/emitSidePanelCloseEvent.ts @@ -0,0 +1,5 @@ +export const SIDE_PANEL_CLOSE_EVENT_NAME = 'side-panel-close'; + +export const emitSidePanelCloseEvent = () => { + window.dispatchEvent(new CustomEvent(SIDE_PANEL_CLOSE_EVENT_NAME)); +}; diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/utils/emitSidePanelOpenEvent.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/utils/emitSidePanelOpenEvent.ts new file mode 100644 index 000000000..ef2467491 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/utils/emitSidePanelOpenEvent.ts @@ -0,0 +1,5 @@ +export const SIDE_PANEL_OPEN_EVENT_NAME = 'side-panel-open'; + +export const emitSidePanelOpenEvent = () => { + window.dispatchEvent(new CustomEvent(SIDE_PANEL_OPEN_EVENT_NAME)); +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/usePushFocusItemToFocusStack.test.tsx b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/usePushFocusItemToFocusStack.test.tsx index 91e0f9a28..1f15da1fc 100644 --- a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/usePushFocusItemToFocusStack.test.tsx +++ b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/usePushFocusItemToFocusStack.test.tsx @@ -43,6 +43,7 @@ describe('usePushFocusItemToFocusStack', () => { enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysConflictingWithKeyboard: true, }, + memoizeKey: 'global', }; await act(async () => { @@ -70,6 +71,7 @@ describe('usePushFocusItemToFocusStack', () => { enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysConflictingWithKeyboard: true, }, + memoizeKey: 'global', }; await act(async () => { diff --git a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useRemoveFocusItemFromFocusStack.test.tsx b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useRemoveFocusItemFromFocusStack.test.tsx deleted file mode 100644 index 83bd5209b..000000000 --- a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useRemoveFocusItemFromFocusStack.test.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; -import { useRemoveFocusItemFromFocusStack } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStack'; -import { currentFocusIdSelector } from '@/ui/utilities/focus/states/currentFocusIdSelector'; -import { focusStackState } from '@/ui/utilities/focus/states/focusStackState'; -import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; -import { renderHook } from '@testing-library/react'; -import { act } from 'react'; -import { RecoilRoot, useRecoilValue } from 'recoil'; - -const renderHooks = () => { - const { result } = renderHook( - () => { - const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); - const { removeFocusItemFromFocusStack } = - useRemoveFocusItemFromFocusStack(); - const focusStack = useRecoilValue(focusStackState); - const currentFocusId = useRecoilValue(currentFocusIdSelector); - - return { - pushFocusItemToFocusStack, - removeFocusItemFromFocusStack, - focusStack, - currentFocusId, - }; - }, - { - wrapper: RecoilRoot, - }, - ); - - return { result }; -}; - -describe('useRemoveFocusItemFromFocusStack', () => { - it('should remove focus item from the stack', async () => { - const { result } = renderHooks(); - - const firstFocusItem = { - focusId: 'first-focus-id', - componentInstance: { - componentType: FocusComponentType.MODAL, - componentInstanceId: 'first-instance-id', - }, - globalHotkeysConfig: { - enableGlobalHotkeysWithModifiers: true, - enableGlobalHotkeysConflictingWithKeyboard: true, - }, - }; - - const secondFocusItem = { - focusId: 'second-focus-id', - componentInstance: { - componentType: FocusComponentType.MODAL, - componentInstanceId: 'second-instance-id', - }, - globalHotkeysConfig: { - enableGlobalHotkeysWithModifiers: true, - enableGlobalHotkeysConflictingWithKeyboard: true, - }, - }; - - await act(async () => { - result.current.pushFocusItemToFocusStack({ - focusId: firstFocusItem.focusId, - component: { - type: firstFocusItem.componentInstance.componentType, - instanceId: firstFocusItem.componentInstance.componentInstanceId, - }, - hotkeyScope: { scope: 'test-scope' }, - memoizeKey: 'global', - }); - }); - - await act(async () => { - result.current.pushFocusItemToFocusStack({ - focusId: secondFocusItem.focusId, - component: { - type: secondFocusItem.componentInstance.componentType, - instanceId: secondFocusItem.componentInstance.componentInstanceId, - }, - hotkeyScope: { scope: 'test-scope' }, - memoizeKey: 'global', - }); - }); - - expect(result.current.focusStack).toEqual([ - firstFocusItem, - secondFocusItem, - ]); - expect(result.current.currentFocusId).toEqual(secondFocusItem.focusId); - - await act(async () => { - result.current.removeFocusItemFromFocusStack({ - focusId: firstFocusItem.focusId, - memoizeKey: 'global', - }); - }); - - expect(result.current.focusStack).toEqual([secondFocusItem]); - expect(result.current.currentFocusId).toEqual(secondFocusItem.focusId); - }); -}); diff --git a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useRemoveFocusItemFromFocusStackById.test.tsx b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useRemoveFocusItemFromFocusStackById.test.tsx new file mode 100644 index 000000000..61cefc7f0 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useRemoveFocusItemFromFocusStackById.test.tsx @@ -0,0 +1,150 @@ +import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; +import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; +import { currentFocusIdSelector } from '@/ui/utilities/focus/states/currentFocusIdSelector'; +import { focusStackState } from '@/ui/utilities/focus/states/focusStackState'; +import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { RecoilRoot, useRecoilValue } from 'recoil'; + +const renderHooks = () => { + const { result } = renderHook( + () => { + const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); + const { removeFocusItemFromFocusStackById } = + useRemoveFocusItemFromFocusStackById(); + const focusStack = useRecoilValue(focusStackState); + const currentFocusId = useRecoilValue(currentFocusIdSelector); + + return { + pushFocusItemToFocusStack, + removeFocusItemFromFocusStackById, + focusStack, + currentFocusId, + }; + }, + { + wrapper: RecoilRoot, + }, + ); + + return { result }; +}; + +const firstFocusItem = { + focusId: 'first-focus-id', + componentInstance: { + componentType: FocusComponentType.MODAL, + componentInstanceId: 'first-instance-id', + }, + globalHotkeysConfig: { + enableGlobalHotkeysWithModifiers: true, + enableGlobalHotkeysConflictingWithKeyboard: true, + }, + memoizeKey: 'global', +}; + +const secondFocusItem = { + focusId: 'second-focus-id', + componentInstance: { + componentType: FocusComponentType.DROPDOWN, + componentInstanceId: 'second-instance-id', + }, + globalHotkeysConfig: { + enableGlobalHotkeysWithModifiers: true, + enableGlobalHotkeysConflictingWithKeyboard: true, + }, + memoizeKey: 'global', +}; + +describe('useRemoveFocusItemFromFocusStackById', () => { + it('should remove focus item from the stack', async () => { + const { result } = renderHooks(); + + await act(async () => { + result.current.pushFocusItemToFocusStack({ + focusId: firstFocusItem.focusId, + component: { + type: firstFocusItem.componentInstance.componentType, + instanceId: firstFocusItem.componentInstance.componentInstanceId, + }, + hotkeyScope: { scope: 'test-scope' }, + memoizeKey: firstFocusItem.memoizeKey, + }); + }); + + await act(async () => { + result.current.pushFocusItemToFocusStack({ + focusId: secondFocusItem.focusId, + component: { + type: secondFocusItem.componentInstance.componentType, + instanceId: secondFocusItem.componentInstance.componentInstanceId, + }, + hotkeyScope: { scope: 'test-scope' }, + memoizeKey: secondFocusItem.memoizeKey, + }); + }); + + expect(result.current.focusStack).toEqual([ + firstFocusItem, + secondFocusItem, + ]); + expect(result.current.currentFocusId).toEqual(secondFocusItem.focusId); + + await act(async () => { + result.current.removeFocusItemFromFocusStackById({ + focusId: firstFocusItem.focusId, + }); + }); + + expect(result.current.focusStack).toEqual([secondFocusItem]); + expect(result.current.currentFocusId).toEqual(secondFocusItem.focusId); + }); + + it('should handle invalid focusId gracefully without errors', async () => { + const { result } = renderHooks(); + + await act(async () => { + result.current.pushFocusItemToFocusStack({ + focusId: firstFocusItem.focusId, + component: { + type: firstFocusItem.componentInstance.componentType, + instanceId: firstFocusItem.componentInstance.componentInstanceId, + }, + hotkeyScope: { scope: 'test-scope' }, + memoizeKey: 'global', + }); + }); + + await act(async () => { + result.current.pushFocusItemToFocusStack({ + focusId: secondFocusItem.focusId, + component: { + type: secondFocusItem.componentInstance.componentType, + instanceId: secondFocusItem.componentInstance.componentInstanceId, + }, + hotkeyScope: { scope: 'test-scope' }, + memoizeKey: 'global', + }); + }); + + const originalFocusStack = result.current.focusStack; + const originalCurrentFocusId = result.current.currentFocusId; + + await act(async () => { + expect(() => { + result.current.removeFocusItemFromFocusStackById({ + focusId: 'invalid-focus-id', + }); + }).not.toThrow(); + }); + + expect(result.current.focusStack).toEqual(originalFocusStack); + expect(result.current.currentFocusId).toEqual(originalCurrentFocusId); + expect(result.current.focusStack).toEqual([ + firstFocusItem, + secondFocusItem, + ]); + expect(result.current.currentFocusId).toEqual(secondFocusItem.focusId); + }); +}); diff --git a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useResetFocusStack.test.tsx b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useResetFocusStack.test.tsx index cc420835e..8a63c41e0 100644 --- a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useResetFocusStack.test.tsx +++ b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useResetFocusStack.test.tsx @@ -44,6 +44,7 @@ describe('useResetFocusStack', () => { enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysConflictingWithKeyboard: true, }, + memoizeKey: 'global', }; await act(async () => { diff --git a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useResetFocusStackToFocusItem.test.tsx b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useResetFocusStackToFocusItem.test.tsx index 617057b75..e956b208c 100644 --- a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useResetFocusStackToFocusItem.test.tsx +++ b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useResetFocusStackToFocusItem.test.tsx @@ -44,6 +44,7 @@ describe('useResetFocusStackToFocusItem', () => { enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysConflictingWithKeyboard: true, }, + memoizeKey: 'global', }; const secondFocusItem = { @@ -56,6 +57,7 @@ describe('useResetFocusStackToFocusItem', () => { enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysConflictingWithKeyboard: true, }, + memoizeKey: 'global', }; await act(async () => { @@ -66,7 +68,7 @@ describe('useResetFocusStackToFocusItem', () => { instanceId: firstFocusItem.componentInstance.componentInstanceId, }, hotkeyScope: { scope: 'test-scope' }, - memoizeKey: 'global', + memoizeKey: firstFocusItem.memoizeKey, }); }); @@ -78,7 +80,7 @@ describe('useResetFocusStackToFocusItem', () => { instanceId: secondFocusItem.componentInstance.componentInstanceId, }, hotkeyScope: { scope: 'test-scope' }, - memoizeKey: 'global', + memoizeKey: secondFocusItem.memoizeKey, }); }); @@ -92,7 +94,7 @@ describe('useResetFocusStackToFocusItem', () => { result.current.resetFocusStackToFocusItem({ focusStackItem: firstFocusItem, hotkeyScope: { scope: 'test-scope' }, - memoizeKey: 'global', + memoizeKey: firstFocusItem.memoizeKey, }); }); diff --git a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/usePushFocusItemToFocusStack.ts b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/usePushFocusItemToFocusStack.ts index a3248b0f1..3453cefb3 100644 --- a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/usePushFocusItemToFocusStack.ts +++ b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/usePushFocusItemToFocusStack.ts @@ -42,7 +42,7 @@ export const usePushFocusItemToFocusStack = () => { globalHotkeysConfig?: Partial; // TODO: Remove this once we've migrated hotkey scopes to the new api hotkeyScope: HotkeyScope; - memoizeKey: string; + memoizeKey?: string; }) => { const focusStackItem: FocusStackItem = { focusId, @@ -57,6 +57,8 @@ export const usePushFocusItemToFocusStack = () => { globalHotkeysConfig?.enableGlobalHotkeysConflictingWithKeyboard ?? true, }, + // TODO: Remove this once we've migrated hotkey scopes to the new api + memoizeKey, }; const currentFocusStack = snapshot diff --git a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackByComponentType.ts b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackByComponentType.ts new file mode 100644 index 000000000..7fe2863f2 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackByComponentType.ts @@ -0,0 +1,55 @@ +import { DEBUG_FOCUS_STACK } from '@/ui/utilities/focus/constants/DebugFocusStack'; +import { focusStackState } from '@/ui/utilities/focus/states/focusStackState'; +import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; +import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; +import { useRecoilCallback } from 'recoil'; +import { logDebug } from '~/utils/logDebug'; + +export const useRemoveLastFocusItemFromFocusStackByComponentType = () => { + const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope(); + + const removeLastFocusItemFromFocusStackByComponentType = useRecoilCallback( + ({ snapshot, set }) => + ({ componentType }: { componentType: FocusComponentType }) => { + const focusStack = snapshot.getLoadable(focusStackState).getValue(); + + const lastMatchingIndex = focusStack.findLastIndex( + (focusStackItem) => + focusStackItem.componentInstance.componentType === componentType, + ); + + if (lastMatchingIndex === -1) { + if (DEBUG_FOCUS_STACK) { + logDebug( + `DEBUG: removeFocusItemFromFocusStackByComponentType - no item found for type ${componentType}`, + { focusStack }, + ); + } + return; + } + + const removedFocusItem = focusStack[lastMatchingIndex]; + const newFocusStack = focusStack.filter( + (_, index) => index !== lastMatchingIndex, + ); + + set(focusStackState, newFocusStack); + + if (DEBUG_FOCUS_STACK) { + logDebug( + `DEBUG: removeFocusItemFromFocusStackByComponentType ${componentType}`, + { + removedFocusItem, + newFocusStack, + }, + ); + } + + // TODO: Remove this once we've migrated hotkey scopes to the new api + goBackToPreviousHotkeyScope(removedFocusItem.memoizeKey); + }, + [goBackToPreviousHotkeyScope], + ); + + return { removeLastFocusItemFromFocusStackByComponentType }; +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStack.ts b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById.ts similarity index 67% rename from packages/twenty-front/src/modules/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStack.ts rename to packages/twenty-front/src/modules/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById.ts index 787c5026f..37a9ddffb 100644 --- a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStack.ts +++ b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById.ts @@ -4,14 +4,22 @@ import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousH import { useRecoilCallback } from 'recoil'; import { logDebug } from '~/utils/logDebug'; -export const useRemoveFocusItemFromFocusStack = () => { +export const useRemoveFocusItemFromFocusStackById = () => { const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope(); - const removeFocusItemFromFocusStack = useRecoilCallback( + const removeFocusItemFromFocusStackById = useRecoilCallback( ({ snapshot, set }) => - ({ focusId, memoizeKey }: { focusId: string; memoizeKey: string }) => { + ({ focusId }: { focusId: string }) => { const focusStack = snapshot.getLoadable(focusStackState).getValue(); + const removedFocusItem = focusStack.find( + (focusStackItem) => focusStackItem.focusId === focusId, + ); + + if (!removedFocusItem) { + return; + } + const newFocusStack = focusStack.filter( (focusStackItem) => focusStackItem.focusId !== focusId, ); @@ -25,10 +33,10 @@ export const useRemoveFocusItemFromFocusStack = () => { } // TODO: Remove this once we've migrated hotkey scopes to the new api - goBackToPreviousHotkeyScope(memoizeKey); + goBackToPreviousHotkeyScope(removedFocusItem.memoizeKey); }, [goBackToPreviousHotkeyScope], ); - return { removeFocusItemFromFocusStack }; + return { removeFocusItemFromFocusStackById }; }; diff --git a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/useResetFocusStackToFocusItem.ts b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/useResetFocusStackToFocusItem.ts index 65a1b0279..3c541f9fe 100644 --- a/packages/twenty-front/src/modules/ui/utilities/focus/hooks/useResetFocusStackToFocusItem.ts +++ b/packages/twenty-front/src/modules/ui/utilities/focus/hooks/useResetFocusStackToFocusItem.ts @@ -1,23 +1,25 @@ import { DEBUG_FOCUS_STACK } from '@/ui/utilities/focus/constants/DebugFocusStack'; import { focusStackState } from '@/ui/utilities/focus/states/focusStackState'; import { FocusStackItem } from '@/ui/utilities/focus/types/FocusStackItem'; -import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState'; +import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { previousHotkeyScopeFamilyState } from '@/ui/utilities/hotkey/states/internal/previousHotkeyScopeFamilyState'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { useRecoilCallback } from 'recoil'; import { logDebug } from '~/utils/logDebug'; export const useResetFocusStackToFocusItem = () => { + const setHotkeyScope = useSetHotkeyScope(); + const resetFocusStackToFocusItem = useRecoilCallback( ({ set }) => ({ focusStackItem, hotkeyScope, - memoizeKey, + memoizeKey = 'global', }: { focusStackItem: FocusStackItem; hotkeyScope: HotkeyScope; - memoizeKey: string; + memoizeKey?: string; }) => { set(focusStackState, [focusStackItem]); @@ -29,9 +31,9 @@ export const useResetFocusStackToFocusItem = () => { // TODO: Remove this once we've migrated hotkey scopes to the new api set(previousHotkeyScopeFamilyState(memoizeKey), null); - set(currentHotkeyScopeState, hotkeyScope); + setHotkeyScope(hotkeyScope.scope, hotkeyScope.customScopes); }, - [], + [setHotkeyScope], ); return { resetFocusStackToFocusItem }; diff --git a/packages/twenty-front/src/modules/ui/utilities/focus/types/FocusComponentType.ts b/packages/twenty-front/src/modules/ui/utilities/focus/types/FocusComponentType.ts index b758bcacd..65518edfc 100644 --- a/packages/twenty-front/src/modules/ui/utilities/focus/types/FocusComponentType.ts +++ b/packages/twenty-front/src/modules/ui/utilities/focus/types/FocusComponentType.ts @@ -3,4 +3,8 @@ export enum FocusComponentType { DROPDOWN = 'dropdown', SIDE_PANEL = 'side-panel', OPEN_FIELD_INPUT = 'open-field-input', + PAGE = 'page', + RECORD_TABLE = 'record-table', + RECORD_TABLE_ROW = 'record-table-row', + RECORD_TABLE_CELL = 'record-table-cell', } diff --git a/packages/twenty-front/src/modules/ui/utilities/focus/types/FocusStackItem.ts b/packages/twenty-front/src/modules/ui/utilities/focus/types/FocusStackItem.ts index 262547f39..e6f866901 100644 --- a/packages/twenty-front/src/modules/ui/utilities/focus/types/FocusStackItem.ts +++ b/packages/twenty-front/src/modules/ui/utilities/focus/types/FocusStackItem.ts @@ -5,4 +5,5 @@ export type FocusStackItem = { focusId: string; componentInstance: FocusComponentInstance; globalHotkeysConfig: GlobalHotkeysConfig; + memoizeKey: string; }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx index c8bf6f184..d9bf1284f 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx @@ -1,6 +1,6 @@ import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant'; -import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose'; +import { useListenToSidePanelClosing } from '@/ui/layout/right-drawer/hooks/useListenToSidePanelClosing'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; @@ -180,7 +180,7 @@ export const WorkflowDiagramCanvasBase = ({ }); }; - useListenRightDrawerClose(() => { + useListenToSidePanelClosing(() => { reactflow.setNodes((nodes) => nodes.map((node) => ({ ...node, selected: false })), );