From 1ab51d41aab5a0bc4217cfd0a519e55e0750f876 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?=
<71827178+bosiraphael@users.noreply.github.com>
Date: Wed, 25 Jun 2025 15:18:51 +0200
Subject: [PATCH] Replace hotkey scopes by focus stack (Part 2 - Record Table,
Rows and Cells) (#12798)
# Replace hotkey scopes by focus stack (Part 2 - Record Table, Rows and
Cells)
This PR is the second part of a refactoring aiming to deprecate the
hotkey scopes api in favor of the new focus stack api which is more
robust.
Part 1: https://github.com/twentyhq/twenty/pull/12673
The record table shortcuts are no longer centralized in the record
table, they now split and the focused element is in charge of applying
the desired shortcuts. (For instance: The rows are in charge of the row
navigation and the cells of the cells navigation).
## Video QA:
https://github.com/user-attachments/assets/f0bb9eed-8a2a-4b6d-a82f-1998e929f122
## Bugfixes:
### Fix record table click outside not working after opening and closing
a cell
Introduced by https://github.com/twentyhq/twenty/pull/11644
#### Before
https://github.com/user-attachments/assets/d28deda8-15e9-4ffe-b60a-e8b54625f8e5
#### After
https://github.com/user-attachments/assets/3f7e1ffc-15d9-4336-aeb0-bebd8ae0cbe0
### Fix ObjectFilterDropdownFilterInput hotkeys
Introduced by https://github.com/twentyhq/twenty/pull/12673
#### Before
https://github.com/user-attachments/assets/ab2039bd-ebe1-49ba-8377-a6b300664469
#### After
https://github.com/user-attachments/assets/90597453-dab2-426b-a134-0a24b0de0a6b
---
.../effect-components/PageChangeEffect.tsx | 36 ++---
...MenuCloseAnimationCompleteCleanup.test.tsx | 10 +-
.../command-menu/hooks/useCommandMenu.ts | 13 +-
...ommandMenuCloseAnimationCompleteCleanup.ts | 4 +-
.../RecordBoardDeactivateBoardCardEffect.tsx | 4 +-
.../constants/RecordIndexFocusId.ts | 1 +
.../hooks/useResetFocusStackToRecordIndex.ts | 37 +++++
.../RecordTableBodyEffectsWrapper.tsx | 8 -
...ordTableDeactivateRecordTableRowEffect.tsx | 4 +-
...ecordTableFocusModeHotkeysSetterEffect.tsx | 87 ----------
...ordTableRecordGroupBodyContextProvider.tsx | 6 +-
.../components/RecordTableWithWrappers.tsx | 17 +-
.../useCloseCurrentTableCellInEditMode.ts | 17 +-
.../hooks/internal/useLeaveTableFocus.ts | 21 ++-
.../hooks/internal/useSetRecordTableData.ts | 15 +-
.../useSetRecordTableFocusPosition.ts | 28 ----
.../hooks/useFocusedRecordTableRow.ts | 102 ++++++++++--
.../hooks/useMapKeyboardToFocus.ts | 80 ----------
.../record-table/hooks/useRecordTable.ts | 8 -
.../record-table/hooks/useRecordTableMove.ts | 39 -----
.../hooks/useRecordTableMoveFocusedCell.ts | 25 ++-
.../hooks/useRecordTableRowFocusHotkeys.ts | 36 +++++
.../record-table/hooks/useTableColumns.ts | 14 +-
.../RecordTableBodyEscapeHotkeyEffect.tsx | 31 ++--
...RecordTableBodyFocusClickOutsideEffect.tsx | 14 +-
.../RecordTableBodyFocusKeyboardEffect.tsx | 56 +------
.../RecordTableBodyRowFocusKeyboardEffect.tsx | 31 ----
.../RecordTableCellArrowKeysEffect.tsx | 14 ++
.../components/RecordTableCellEditMode.tsx | 6 +-
.../RecordTableCellEditModePortal.tsx | 4 +-
.../RecordTableCellHotkeysEffect.tsx | 118 ++++++++++++++
.../components/RecordTableCellPortals.tsx | 8 +-
.../useMoveHoverToCurrentCell.test.tsx | 4 +-
.../useSetIsRecordTableFocusActive.test.tsx | 40 ++---
.../useCloseRecordTableCellInGroup.test.tsx | 11 --
.../useCloseRecordTableCellNoGroup.test.tsx | 11 --
.../useCloseRecordTableCellInGroup.ts | 37 ++---
.../useCloseRecordTableCellNoGroup.ts | 51 +++---
...eCurrentlyFocusedRecordTableCellFocusId.ts | 18 +++
.../hooks/useFocusRecordTableCell.ts | 88 ++++++++++
.../hooks/useMoveHoverToCurrentCell.ts | 1 -
.../hooks/useOpenRecordTableCellV2.ts | 8 +-
.../hooks/useRecordTableCellFocusHotkeys.ts | 56 +++++++
... => useSetIsRecordTableCellFocusActive.ts} | 34 ++--
.../hooks/useUnfocusRecordTableCell.ts | 59 +++++++
.../utils/getRecordTableCellFocusId.ts | 11 ++
.../useOpenRecordFilterChipFromTableHeader.ts | 8 +-
.../components/RecordTableRow.tsx | 8 +-
.../RecordTableRowArrowKeysEffect.tsx | 14 ++
.../components/RecordTableRowHotkeyEffect.tsx | 74 +--------
.../hooks/useRecordTableRowFocusId.ts | 14 ++
.../hooks/useRecordTableRowHotkeys.ts | 145 +++++++++++++++++
.../utils/getRecordTableRowFocusId.ts | 9 ++
.../record-table/types/TableHotkeyScope.ts | 3 -
.../__stories__/Dropdown.stories.tsx | 1 +
.../dropdown/hooks/useCloseAnyOpenDropdown.ts | 13 +-
.../ui/layout/dropdown/hooks/useDropdown.ts | 11 +-
.../ui/layout/dropdown/hooks/useDropdownV2.ts | 10 +-
.../hooks/useOpenDropdownFromOutside.ts | 36 -----
.../__stories__/ConfirmationModal.stories.tsx | 1 +
.../components/__stories__/Modal.stories.tsx | 1 +
.../ui/layout/modal/hooks/useModal.tsx | 10 +-
.../hooks/useListenRightDrawerClose.ts | 12 --
.../hooks/useListenToSidePanelClosing.ts | 12 ++
.../hooks/useListenToSidePanelOpening.ts | 12 ++
.../utils/emitRightDrawerCloseEvent.ts | 5 -
.../utils/emitSidePanelCloseEvent.ts | 5 +
.../utils/emitSidePanelOpenEvent.ts | 5 +
.../usePushFocusItemToFocusStack.test.tsx | 2 +
.../useRemoveFocusItemFromFocusStack.test.tsx | 102 ------------
...RemoveFocusItemFromFocusStackById.test.tsx | 150 ++++++++++++++++++
.../__tests__/useResetFocusStack.test.tsx | 1 +
.../useResetFocusStackToFocusItem.test.tsx | 8 +-
.../hooks/usePushFocusItemToFocusStack.ts | 4 +-
...eFocusItemFromFocusStackByComponentType.ts | 55 +++++++
...> useRemoveFocusItemFromFocusStackById.ts} | 18 ++-
.../hooks/useResetFocusStackToFocusItem.ts | 12 +-
.../focus/types/FocusComponentType.ts | 4 +
.../utilities/focus/types/FocusStackItem.ts | 1 +
.../components/WorkflowDiagramCanvasBase.tsx | 4 +-
80 files changed, 1246 insertions(+), 847 deletions(-)
create mode 100644 packages/twenty-front/src/modules/object-record/record-index/constants/RecordIndexFocusId.ts
create mode 100644 packages/twenty-front/src/modules/object-record/record-index/hooks/useResetFocusStackToRecordIndex.ts
delete mode 100644 packages/twenty-front/src/modules/object-record/record-table/components/RecordTableFocusModeHotkeysSetterEffect.tsx
delete mode 100644 packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableFocusPosition.ts
delete mode 100644 packages/twenty-front/src/modules/object-record/record-table/hooks/useMapKeyboardToFocus.ts
delete mode 100644 packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMove.ts
create mode 100644 packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableRowFocusHotkeys.ts
delete mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyRowFocusKeyboardEffect.tsx
create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellArrowKeysEffect.tsx
create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellHotkeysEffect.tsx
create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useCurrentlyFocusedRecordTableCellFocusId.ts
create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useFocusRecordTableCell.ts
create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useRecordTableCellFocusHotkeys.ts
rename packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/{useSetIsRecordTableFocusActive.ts => useSetIsRecordTableCellFocusActive.ts} (53%)
create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useUnfocusRecordTableCell.ts
create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-cell/utils/getRecordTableCellFocusId.ts
create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowArrowKeysEffect.tsx
create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-row/hooks/useRecordTableRowFocusId.ts
create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-row/hooks/useRecordTableRowHotkeys.ts
create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-row/utils/getRecordTableRowFocusId.ts
delete mode 100644 packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useOpenDropdownFromOutside.ts
delete mode 100644 packages/twenty-front/src/modules/ui/layout/right-drawer/hooks/useListenRightDrawerClose.ts
create mode 100644 packages/twenty-front/src/modules/ui/layout/right-drawer/hooks/useListenToSidePanelClosing.ts
create mode 100644 packages/twenty-front/src/modules/ui/layout/right-drawer/hooks/useListenToSidePanelOpening.ts
delete mode 100644 packages/twenty-front/src/modules/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent.ts
create mode 100644 packages/twenty-front/src/modules/ui/layout/right-drawer/utils/emitSidePanelCloseEvent.ts
create mode 100644 packages/twenty-front/src/modules/ui/layout/right-drawer/utils/emitSidePanelOpenEvent.ts
delete mode 100644 packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useRemoveFocusItemFromFocusStack.test.tsx
create mode 100644 packages/twenty-front/src/modules/ui/utilities/focus/hooks/__tests__/useRemoveFocusItemFromFocusStackById.test.tsx
create mode 100644 packages/twenty-front/src/modules/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackByComponentType.ts
rename packages/twenty-front/src/modules/ui/utilities/focus/hooks/{useRemoveFocusItemFromFocusStack.ts => useRemoveFocusItemFromFocusStackById.ts} (67%)
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 })),
);