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
This commit is contained in:
Raphaël Bosi
2025-06-25 15:18:51 +02:00
committed by GitHub
parent 6450e11f1e
commit 1ab51d41aa
80 changed files with 1246 additions and 847 deletions

View File

@ -24,7 +24,7 @@ import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlur
import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard'; import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard';
import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard'; import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard';
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; 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 { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow'; import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow';
import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; 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 { useInitializeQueryParamState } from '~/modules/app/hooks/useInitializeQueryParamState';
import { isMatchingLocation } from '~/utils/isMatchingLocation'; import { isMatchingLocation } from '~/utils/isMatchingLocation';
import { getPageTitleFromPath } from '~/utils/title-utils'; import { getPageTitleFromPath } from '~/utils/title-utils';
// TODO: break down into smaller functions and / or hooks // TODO: break down into smaller functions and / or hooks
// - moved usePageChangeEffectNavigateLocation into dedicated hook // - moved usePageChangeEffectNavigateLocation into dedicated hook
export const PageChangeEffect = () => { export const PageChangeEffect = () => {
@ -91,6 +92,8 @@ export const PageChangeEffect = () => {
const { closeCommandMenu } = useCommandMenu(); const { closeCommandMenu } = useCommandMenu();
const { resetFocusStackToRecordIndex } = useResetFocusStackToRecordIndex();
useEffect(() => { useEffect(() => {
closeCommandMenu(); closeCommandMenu();
}, [location.pathname, closeCommandMenu]); }, [location.pathname, closeCommandMenu]);
@ -130,25 +133,10 @@ export const PageChangeEffect = () => {
unfocusBoardCard(); unfocusBoardCard();
} }
} }
}, [
previousLocation,
resetTableSelections,
unfocusRecordTableRow,
deactivateRecordTableRow,
contextStoreCurrentViewType,
resetRecordSelection,
deactivateBoardCard,
unfocusBoardCard,
]);
useEffect(() => {
switch (true) { switch (true) {
case isMatchingLocation(location, AppPath.RecordIndexPage): { case isMatchingLocation(location, AppPath.RecordIndexPage): {
setHotkeyScope(RecordIndexHotkeyScope.RecordIndex, { resetFocusStackToRecordIndex();
goto: true,
keyboardShortcutMenu: true,
searchRecords: true,
});
break; break;
} }
case isMatchingLocation(location, AppPath.RecordShowPage): { case isMatchingLocation(location, AppPath.RecordShowPage): {
@ -198,7 +186,19 @@ export const PageChangeEffect = () => {
break; break;
} }
} }
}, [location, setHotkeyScope]); }, [
location,
setHotkeyScope,
previousLocation,
contextStoreCurrentViewType,
resetTableSelections,
unfocusRecordTableRow,
deactivateRecordTableRow,
resetRecordSelection,
deactivateBoardCard,
unfocusBoardCard,
resetFocusStackToRecordIndex,
]);
useEffect(() => { useEffect(() => {
setTimeout(() => { setTimeout(() => {

View File

@ -23,7 +23,7 @@ import { IconList } from 'twenty-ui/display';
const mockCloseDropdown = jest.fn(); const mockCloseDropdown = jest.fn();
const mockResetContextStoreStates = jest.fn(); const mockResetContextStoreStates = jest.fn();
const mockResetSelectedItem = jest.fn(); const mockResetSelectedItem = jest.fn();
const mockEmitRightDrawerCloseEvent = jest.fn(); const mockEmitSidePanelCloseEvent = jest.fn();
jest.mock('@/ui/layout/dropdown/hooks/useDropdownV2', () => ({ jest.mock('@/ui/layout/dropdown/hooks/useDropdownV2', () => ({
useDropdownV2: () => ({ useDropdownV2: () => ({
@ -43,9 +43,9 @@ jest.mock('@/ui/layout/selectable-list/hooks/useSelectableList', () => ({
}), }),
})); }));
jest.mock('@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent', () => ({ jest.mock('@/ui/layout/right-drawer/utils/emitSidePanelCloseEvent', () => ({
emitRightDrawerCloseEvent: () => { emitSidePanelCloseEvent: () => {
mockEmitRightDrawerCloseEvent(); mockEmitSidePanelCloseEvent();
}, },
})); }));
@ -224,7 +224,7 @@ describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
expect(mockCloseDropdown).toHaveBeenCalledTimes(1); expect(mockCloseDropdown).toHaveBeenCalledTimes(1);
expect(mockResetContextStoreStates).toHaveBeenCalledTimes(2); expect(mockResetContextStoreStates).toHaveBeenCalledTimes(2);
expect(mockResetSelectedItem).toHaveBeenCalledTimes(1); expect(mockResetSelectedItem).toHaveBeenCalledTimes(1);
expect(mockEmitRightDrawerCloseEvent).toHaveBeenCalledTimes(1); expect(mockEmitSidePanelCloseEvent).toHaveBeenCalledTimes(1);
expect(mockCloseDropdown).toHaveBeenCalledWith( expect(mockCloseDropdown).toHaveBeenCalledWith(
COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID, COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID,

View File

@ -2,14 +2,14 @@ import { useRecoilCallback } from 'recoil';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; 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 { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu'; import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState'; import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown'; 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 { 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 { useCallback } from 'react';
import { IconDotsVertical } from 'twenty-ui/display'; import { IconDotsVertical } from 'twenty-ui/display';
import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState'; import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
@ -18,7 +18,8 @@ export const useCommandMenu = () => {
const { navigateCommandMenu } = useNavigateCommandMenu(); const { navigateCommandMenu } = useNavigateCommandMenu();
const { closeAnyOpenDropdown } = useCloseAnyOpenDropdown(); const { closeAnyOpenDropdown } = useCloseAnyOpenDropdown();
const { removeFocusItemFromFocusStack } = useRemoveFocusItemFromFocusStack(); const { removeFocusItemFromFocusStackById } =
useRemoveFocusItemFromFocusStackById();
const closeCommandMenu = useRecoilCallback( const closeCommandMenu = useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
@ -32,16 +33,16 @@ export const useCommandMenu = () => {
set(isCommandMenuClosingState, true); set(isCommandMenuClosingState, true);
set(isDragSelectionStartEnabledState, true); set(isDragSelectionStartEnabledState, true);
closeAnyOpenDropdown(); closeAnyOpenDropdown();
removeFocusItemFromFocusStack({ removeFocusItemFromFocusStackById({
focusId: SIDE_PANEL_FOCUS_ID, focusId: SIDE_PANEL_FOCUS_ID,
memoizeKey: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}); });
} }
}, },
[closeAnyOpenDropdown, removeFocusItemFromFocusStack], [closeAnyOpenDropdown, removeFocusItemFromFocusStackById],
); );
const openCommandMenu = useCallback(() => { const openCommandMenu = useCallback(() => {
emitSidePanelOpenEvent();
closeAnyOpenDropdown(); closeAnyOpenDropdown();
navigateCommandMenu({ navigateCommandMenu({
page: CommandMenuPages.Root, page: CommandMenuPages.Root,

View File

@ -14,7 +14,7 @@ import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpe
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; 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 { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId'; import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
@ -52,7 +52,7 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
resetSelectedItem(); resetSelectedItem();
set(hasUserSelectedCommandState, false); set(hasUserSelectedCommandState, false);
emitRightDrawerCloseEvent(); emitSidePanelCloseEvent();
set(isCommandMenuClosingState, false); set(isCommandMenuClosingState, false);
set( set(
activeTabIdComponentState.atomFamily({ activeTabIdComponentState.atomFamily({

View File

@ -1,13 +1,13 @@
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard'; 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'; import { useContext } from 'react';
export const RecordBoardDeactivateBoardCardEffect = () => { export const RecordBoardDeactivateBoardCardEffect = () => {
const { recordBoardId } = useContext(RecordBoardContext); const { recordBoardId } = useContext(RecordBoardContext);
const { deactivateBoardCard } = useActiveRecordBoardCard(recordBoardId); const { deactivateBoardCard } = useActiveRecordBoardCard(recordBoardId);
useListenRightDrawerClose(() => { useListenToSidePanelClosing(() => {
deactivateBoardCard(); deactivateBoardCard();
}); });

View File

@ -0,0 +1 @@
export const RECORD_INDEX_FOCUS_ID = 'record-index';

View File

@ -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,
};
};

View File

@ -2,11 +2,8 @@ import { RecordTableDeactivateRecordTableRowEffect } from '@/object-record/recor
import { RecordTableBodyEscapeHotkeyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyEscapeHotkeyEffect'; 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 { RecordTableBodyFocusClickOutsideEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyFocusClickOutsideEffect';
import { RecordTableBodyFocusKeyboardEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyFocusKeyboardEffect'; 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 { RecordTableNoRecordGroupBodyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect';
import { RecordTableRecordGroupBodyEffects } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffects'; 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 { export interface RecordTableBodyEffectsWrapperProps {
hasRecordGroups: boolean; hasRecordGroups: boolean;
@ -17,10 +14,6 @@ export const RecordTableBodyEffectsWrapper = ({
hasRecordGroups, hasRecordGroups,
tableBodyRef, tableBodyRef,
}: RecordTableBodyEffectsWrapperProps) => { }: RecordTableBodyEffectsWrapperProps) => {
const isRecordTableRowFocusActive = useRecoilComponentValueV2(
isRecordTableRowFocusActiveComponentState,
);
return ( return (
<> <>
{hasRecordGroups ? ( {hasRecordGroups ? (
@ -30,7 +23,6 @@ export const RecordTableBodyEffectsWrapper = ({
)} )}
<RecordTableBodyEscapeHotkeyEffect /> <RecordTableBodyEscapeHotkeyEffect />
<RecordTableBodyFocusKeyboardEffect /> <RecordTableBodyFocusKeyboardEffect />
{isRecordTableRowFocusActive && <RecordTableBodyRowFocusKeyboardEffect />}
<RecordTableBodyFocusClickOutsideEffect tableBodyRef={tableBodyRef} /> <RecordTableBodyFocusClickOutsideEffect tableBodyRef={tableBodyRef} />
<RecordTableDeactivateRecordTableRowEffect /> <RecordTableDeactivateRecordTableRowEffect />
</> </>

View File

@ -1,10 +1,10 @@
import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow'; 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 = () => { export const RecordTableDeactivateRecordTableRowEffect = () => {
const { deactivateRecordTableRow } = useActiveRecordTableRow(); const { deactivateRecordTableRow } = useActiveRecordTableRow();
useListenRightDrawerClose(() => { useListenToSidePanelClosing(() => {
deactivateRecordTableRow(); deactivateRecordTableRow();
}); });

View File

@ -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 <></>;
};

View File

@ -1,7 +1,7 @@
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext'; import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter'; 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 { 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 { useMoveHoverToCurrentCell } from '@/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell';
import { import {
@ -29,10 +29,10 @@ export const RecordTableRecordGroupBodyContextProvider = ({
openTableCell(args); openTableCell(args);
}; };
const { move } = useRecordTableMove(recordTableId); const { moveFocus } = useRecordTableMoveFocusedCell(recordTableId);
const handleMoveFocus = (direction: MoveFocusDirection) => { const handleMoveFocus = (direction: MoveFocusDirection) => {
move(direction); moveFocus(direction);
}; };
const { closeTableCellInGroup } = useCloseRecordTableCellInGroup(); const { closeTableCellInGroup } = useCloseRecordTableCellInGroup();

View File

@ -13,7 +13,7 @@ import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinit
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider'; import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
import { 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 { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext'; import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext';
import { useRecordTable } from '../hooks/useRecordTable'; import { useRecordTable } from '../hooks/useRecordTable';
@ -58,15 +58,16 @@ export const RecordTableWithWrappers = ({
}, },
); );
useScopedHotkeys( useHotkeysOnFocusedElement({
'ctrl+a,meta+a', keys: ['ctrl+a,meta+a'],
handleSelectAllRows, callback: handleSelectAllRows,
TableHotkeyScope.TableFocus, focusId: recordTableId,
[], scope: RecordIndexHotkeyScope.RecordIndex,
{ dependencies: [handleSelectAllRows],
options: {
enableOnFormTags: false, enableOnFormTags: false,
}, },
); });
const { saveViewFields } = useSaveCurrentViewFields(); const { saveViewFields } = useSaveCurrentViewFields();

View File

@ -2,6 +2,8 @@ import { useRecoilCallback } from 'recoil';
import { recordTableCellEditModePositionComponentState } from '@/object-record/record-table/states/recordTableCellEditModePositionComponentState'; import { recordTableCellEditModePositionComponentState } from '@/object-record/record-table/states/recordTableCellEditModePositionComponentState';
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId'; 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'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
export const useCloseCurrentTableCellInEditMode = (recordTableId?: string) => { export const useCloseCurrentTableCellInEditMode = (recordTableId?: string) => {
@ -14,14 +16,25 @@ export const useCloseCurrentTableCellInEditMode = (recordTableId?: string) => {
const { goBackToPreviousDropdownFocusId } = const { goBackToPreviousDropdownFocusId } =
useGoBackToPreviousDropdownFocusId(); useGoBackToPreviousDropdownFocusId();
const { removeLastFocusItemFromFocusStackByComponentType } =
useRemoveLastFocusItemFromFocusStackByComponentType();
return useRecoilCallback( return useRecoilCallback(
({ set }) => { ({ set }) => {
return async () => { return () => {
set(currentTableCellInEditModePositionState, null); set(currentTableCellInEditModePositionState, null);
goBackToPreviousDropdownFocusId(); goBackToPreviousDropdownFocusId();
removeLastFocusItemFromFocusStackByComponentType({
componentType: FocusComponentType.OPEN_FIELD_INPUT,
});
}; };
}, },
[currentTableCellInEditModePositionState, goBackToPreviousDropdownFocusId], [
currentTableCellInEditModePositionState,
goBackToPreviousDropdownFocusId,
removeLastFocusItemFromFocusStackByComponentType,
],
); );
}; };

View File

@ -1,7 +1,8 @@
import { useResetFocusStackToRecordIndex } from '@/object-record/record-index/hooks/useResetFocusStackToRecordIndex';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow'; import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow';
import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; 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 { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState'; import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
@ -17,10 +18,6 @@ export const useLeaveTableFocus = (recordTableId?: string) => {
recordTableIdFromContext, recordTableIdFromContext,
); );
const { setIsFocusActiveForCurrentPosition } = useSetIsRecordTableFocusActive(
recordTableIdFromContext,
);
const setRecordTableHoverPosition = useSetRecoilComponentStateV2( const setRecordTableHoverPosition = useSetRecoilComponentStateV2(
recordTableHoverPositionComponentState, recordTableHoverPositionComponentState,
recordTableIdFromContext, recordTableIdFromContext,
@ -34,15 +31,23 @@ export const useLeaveTableFocus = (recordTableId?: string) => {
recordTableIdFromContext, recordTableIdFromContext,
); );
return () => { const { resetFocusStackToRecordIndex } = useResetFocusStackToRecordIndex();
resetTableRowSelection();
setIsFocusActiveForCurrentPosition(false); const { unfocusRecordTableCell } = useUnfocusRecordTableCell(
recordTableIdFromContext,
);
return () => {
unfocusRecordTableCell();
resetTableRowSelection();
unfocusRecordTableRow(); unfocusRecordTableRow();
deactivateRecordTableRow(); deactivateRecordTableRow();
setRecordTableHoverPosition(null); setRecordTableHoverPosition(null);
resetFocusStackToRecordIndex();
}; };
}; };

View File

@ -4,7 +4,8 @@ import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; 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 { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState';
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState'; import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState'; import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState';
@ -44,14 +45,14 @@ export const useSetRecordTableData = ({
recordTableId, recordTableId,
); );
const { setIsFocusActiveForCurrentPosition } =
useSetIsRecordTableFocusActive(recordTableId);
const setRecordTableHoverPosition = useSetRecoilComponentStateV2( const setRecordTableHoverPosition = useSetRecoilComponentStateV2(
recordTableHoverPositionComponentState, recordTableHoverPositionComponentState,
recordTableId, recordTableId,
); );
const { unfocusRecordTableCell } = useUnfocusRecordTableCell(recordTableId);
const { unfocusRecordTableRow } = useFocusedRecordTableRow(recordTableId);
return useRecoilCallback( return useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
<T extends ObjectRecord>({ <T extends ObjectRecord>({
@ -92,7 +93,8 @@ export const useSetRecordTableData = ({
const recordIds = records.map((record) => record.id); const recordIds = records.map((record) => record.id);
if (!isDeeplyEqual(currentRowIds, recordIds)) { if (!isDeeplyEqual(currentRowIds, recordIds)) {
setIsFocusActiveForCurrentPosition(false); unfocusRecordTableCell();
unfocusRecordTableRow();
setRecordTableHoverPosition(null); setRecordTableHoverPosition(null);
if (hasUserSelectedAllRows) { if (hasUserSelectedAllRows) {
@ -115,7 +117,8 @@ export const useSetRecordTableData = ({
recordIndexRecordIdsByGroupFamilyState, recordIndexRecordIdsByGroupFamilyState,
recordIndexAllRecordIdsSelector, recordIndexAllRecordIdsSelector,
hasUserSelectedAllRowsState, hasUserSelectedAllRowsState,
setIsFocusActiveForCurrentPosition, unfocusRecordTableCell,
unfocusRecordTableRow,
setRecordTableHoverPosition, setRecordTableHoverPosition,
isRowSelectedFamilyState, isRowSelectedFamilyState,
], ],

View File

@ -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],
);
};

View File

@ -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 { focusedRecordTableRowIndexComponentState } from '@/object-record/record-table/states/focusedRecordTableRowIndexComponentState';
import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState'; import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState';
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState'; import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState'; import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState'; 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 { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
export const useFocusedRecordTableRow = (recordTableId?: string) => { export const useFocusedRecordTableRow = (recordTableId?: string) => {
const recordTableIdFromContext = useAvailableComponentInstanceIdOrThrow(
RecordTableComponentInstanceContext,
recordTableId,
);
const isRowFocusedState = useRecoilComponentCallbackStateV2( const isRowFocusedState = useRecoilComponentCallbackStateV2(
isRecordTableRowFocusedComponentFamilyState, isRecordTableRowFocusedComponentFamilyState,
recordTableId, recordTableIdFromContext,
); );
const focusedRowIndexState = useRecoilComponentCallbackStateV2( const focusedRowIndexState = useRecoilComponentCallbackStateV2(
focusedRecordTableRowIndexComponentState, focusedRecordTableRowIndexComponentState,
recordTableId, recordTableIdFromContext,
); );
const isRowFocusActiveState = useRecoilComponentCallbackStateV2( const isRowFocusActiveState = useRecoilComponentCallbackStateV2(
isRecordTableRowFocusActiveComponentState, isRecordTableRowFocusActiveComponentState,
recordTableId, recordTableIdFromContext,
); );
const focusedCellPositionState = useRecoilComponentCallbackStateV2( const focusedCellPositionState = useRecoilComponentCallbackStateV2(
recordTableFocusPositionComponentState, recordTableFocusPositionComponentState,
recordTableId, recordTableIdFromContext,
); );
const isRecordTableCellFocusActiveState = useRecoilComponentCallbackStateV2( const isRecordTableCellFocusActiveState = useRecoilComponentCallbackStateV2(
isRecordTableCellFocusActiveComponentState, isRecordTableCellFocusActiveComponentState,
recordTableId, recordTableIdFromContext,
);
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const { removeFocusItemFromFocusStackById } =
useRemoveFocusItemFromFocusStackById();
const { unfocusRecordTableCell } = useUnfocusRecordTableCell(
recordTableIdFromContext,
); );
const unfocusRecordTableRow = useRecoilCallback( const unfocusRecordTableRow = useRecoilCallback(
@ -44,11 +66,26 @@ export const useFocusedRecordTableRow = (recordTableId?: string) => {
return; return;
} }
const focusId = getRecordTableRowFocusId({
recordTableId: recordTableIdFromContext,
rowIndex: focusedRowIndex,
});
removeFocusItemFromFocusStackById({
focusId,
});
set(focusedRowIndexState, null); set(focusedRowIndexState, null);
set(isRowFocusedState(focusedRowIndex), false); set(isRowFocusedState(focusedRowIndex), false);
set(isRowFocusActiveState, false); set(isRowFocusActiveState, false);
}, },
[focusedRowIndexState, isRowFocusedState, isRowFocusActiveState], [
focusedRowIndexState,
isRowFocusedState,
isRowFocusActiveState,
recordTableIdFromContext,
removeFocusItemFromFocusStackById,
],
); );
const focusRecordTableRow = useRecoilCallback( const focusRecordTableRow = useRecoilCallback(
@ -60,13 +97,51 @@ export const useFocusedRecordTableRow = (recordTableId?: string) => {
if (isDefined(focusedRowIndex) && focusedRowIndex !== rowIndex) { if (isDefined(focusedRowIndex) && focusedRowIndex !== rowIndex) {
set(isRowFocusedState(focusedRowIndex), false); 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(focusedRowIndexState, rowIndex);
set(isRowFocusedState(rowIndex), true); set(isRowFocusedState(rowIndex), true);
set(isRowFocusActiveState, true); set(isRowFocusActiveState, true);
}, },
[focusedRowIndexState, isRowFocusedState, isRowFocusActiveState], [
focusedRowIndexState,
recordTableIdFromContext,
pushFocusItemToFocusStack,
isRowFocusedState,
isRowFocusActiveState,
removeFocusItemFromFocusStackById,
],
); );
const restoreRecordTableRowFocusFromCellPosition = useRecoilCallback( const restoreRecordTableRowFocusFromCellPosition = useRecoilCallback(
@ -84,20 +159,21 @@ export const useFocusedRecordTableRow = (recordTableId?: string) => {
.getLoadable(isRecordTableCellFocusActiveState) .getLoadable(isRecordTableCellFocusActiveState)
.getValue(); .getValue();
if ( if (!isDefined(focusedCellPosition) || !isRecordTableCellFocusActive) {
!isDefined(focusedRowIndex) ||
!isDefined(focusedCellPosition) ||
!isRecordTableCellFocusActive
) {
return; return;
} }
focusRecordTableRow(focusedCellPosition.row); unfocusRecordTableCell();
if (isDefined(focusedRowIndex)) {
focusRecordTableRow(focusedCellPosition.row);
}
}, },
[ [
focusedRowIndexState, focusedRowIndexState,
focusedCellPositionState, focusedCellPositionState,
isRecordTableCellFocusActiveState, isRecordTableCellFocusActiveState,
unfocusRecordTableCell,
focusRecordTableRow, focusRecordTableRow,
], ],
); );

View File

@ -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],
);
};

View File

@ -12,7 +12,6 @@ import { RecordTableComponentInstanceContext } from '@/object-record/record-tabl
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState'; import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { onColumnsChangeComponentState } from '@/object-record/record-table/states/onColumnsChangeComponentState'; 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 { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; 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 { useLeaveTableFocus } from './internal/useLeaveTableFocus';
import { useResetTableRowSelection } from './internal/useResetTableRowSelection'; import { useResetTableRowSelection } from './internal/useResetTableRowSelection';
import { useSelectAllRows } from './internal/useSelectAllRows'; import { useSelectAllRows } from './internal/useSelectAllRows';
import { useSetRecordTableFocusPosition } from './internal/useSetRecordTableFocusPosition';
import { useSetRowSelectedState } from './internal/useSetRowSelectedState'; import { useSetRowSelectedState } from './internal/useSetRowSelectedState';
type useRecordTableProps = { type useRecordTableProps = {
recordTableId?: string; recordTableId?: string;
@ -94,10 +92,6 @@ export const useRecordTable = (props?: useRecordTableProps) => {
const resetTableRowSelection = useResetTableRowSelection(recordTableId); const resetTableRowSelection = useResetTableRowSelection(recordTableId);
const setFocusPosition = useSetRecordTableFocusPosition(recordTableId);
const { move } = useRecordTableMove(recordTableId);
const { selectAllRows } = useSelectAllRows(recordTableId); const { selectAllRows } = useSelectAllRows(recordTableId);
return { return {
@ -106,11 +100,9 @@ export const useRecordTable = (props?: useRecordTableProps) => {
leaveTableFocus, leaveTableFocus,
setRowSelected, setRowSelected,
resetTableRowSelection, resetTableRowSelection,
move,
selectAllRows, selectAllRows,
setOnColumnsChange, setOnColumnsChange,
setIsRecordTableInitialLoading, setIsRecordTableInitialLoading,
setFocusPosition,
setHasUserSelectedAllRows, setHasUserSelectedAllRows,
setOnToggleColumnSort, setOnToggleColumnSort,
}; };

View File

@ -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,
};
};

View File

@ -4,13 +4,13 @@ import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocus
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; 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 { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
import { numberOfTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/numberOfTableColumnsComponentSelector'; import { numberOfTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/numberOfTableColumnsComponentSelector';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
export const useRecordTableMoveFocusedCell = (recordTableId?: string) => { export const useRecordTableMoveFocusedCell = (recordTableId?: string) => {
const setFocusPosition = useSetRecordTableFocusPosition(recordTableId); const { focusRecordTableCell } = useFocusRecordTableCell(recordTableId);
const focusPositionState = useRecoilComponentCallbackStateV2( const focusPositionState = useRecoilComponentCallbackStateV2(
recordTableFocusPositionComponentState, recordTableFocusPositionComponentState,
@ -33,12 +33,12 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => {
newRowIndex = 0; newRowIndex = 0;
} }
setFocusPosition({ focusRecordTableCell({
...focusPosition, ...focusPosition,
row: newRowIndex, row: newRowIndex,
}); });
}, },
[focusPositionState, setFocusPosition], [focusPositionState, focusRecordTableCell],
); );
const moveDown = useRecoilCallback( const moveDown = useRecoilCallback(
@ -56,12 +56,12 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => {
newRowIndex = allRecordIds.length - 1; newRowIndex = allRecordIds.length - 1;
} }
setFocusPosition({ focusRecordTableCell({
...focusPosition, ...focusPosition,
row: newRowIndex, row: newRowIndex,
}); });
}, },
[recordIndexAllRecordIdsSelector, setFocusPosition, focusPositionState], [recordIndexAllRecordIdsSelector, focusRecordTableCell, focusPositionState],
); );
const numberOfTableColumnsSelector = useRecoilComponentCallbackStateV2( const numberOfTableColumnsSelector = useRecoilComponentCallbackStateV2(
@ -101,12 +101,12 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => {
} }
if (isNotLastColumn) { if (isNotLastColumn) {
setFocusPosition({ focusRecordTableCell({
row: currentRowIndex, row: currentRowIndex,
column: currentColumnIndex + 1, column: currentColumnIndex + 1,
}); });
} else if (isLastColumnButNotLastRow) { } else if (isLastColumnButNotLastRow) {
setFocusPosition({ focusRecordTableCell({
row: currentRowIndex + 1, row: currentRowIndex + 1,
column: 0, column: 0,
}); });
@ -116,7 +116,7 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => {
recordIndexAllRecordIdsSelector, recordIndexAllRecordIdsSelector,
focusPositionState, focusPositionState,
numberOfTableColumnsSelector, numberOfTableColumnsSelector,
setFocusPosition, focusRecordTableCell,
], ],
); );
@ -146,18 +146,18 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => {
} }
if (isNotFirstColumn) { if (isNotFirstColumn) {
setFocusPosition({ focusRecordTableCell({
row: currentRowIndex, row: currentRowIndex,
column: currentColumnIndex - 1, column: currentColumnIndex - 1,
}); });
} else if (isFirstColumnButNotFirstRow) { } else if (isFirstColumnButNotFirstRow) {
setFocusPosition({ focusRecordTableCell({
row: currentRowIndex - 1, row: currentRowIndex - 1,
column: numberOfTableColumns - 1, column: numberOfTableColumns - 1,
}); });
} }
}, },
[numberOfTableColumnsSelector, focusPositionState, setFocusPosition], [numberOfTableColumnsSelector, focusPositionState, focusRecordTableCell],
); );
const moveFocus = (direction: MoveFocusDirection) => { const moveFocus = (direction: MoveFocusDirection) => {
@ -182,7 +182,6 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => {
moveLeft, moveLeft,
moveRight, moveRight,
moveUp, moveUp,
setFocusPosition,
moveFocus, moveFocus,
}; };
}; };

View File

@ -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],
});
};

View File

@ -2,6 +2,7 @@ import { useCallback } from 'react';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; 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 { useMoveViewColumns } from '@/views/hooks/useMoveViewColumns';
import { useSetTableColumns } from '@/object-record/record-table/hooks/useSetTableColumns'; import { useSetTableColumns } from '@/object-record/record-table/hooks/useSetTableColumns';
@ -40,6 +41,10 @@ export const useTableColumns = (props?: useRecordTableProps) => {
const { handleColumnMove } = useMoveViewColumns(); const { handleColumnMove } = useMoveViewColumns();
const { unfocusRecordTableCell } = useUnfocusRecordTableCell(
props?.recordTableId,
);
const instanceId = useAvailableComponentInstanceIdOrThrow( const instanceId = useAvailableComponentInstanceIdOrThrow(
RecordTableComponentInstanceContext, RecordTableComponentInstanceContext,
props?.recordTableId, props?.recordTableId,
@ -106,6 +111,8 @@ export const useTableColumns = (props?: useRecordTableProps) => {
direction: 'left' | 'right', direction: 'left' | 'right',
column: ColumnDefinition<FieldMetadata>, column: ColumnDefinition<FieldMetadata>,
) => { ) => {
unfocusRecordTableCell();
const currentColumnArrayIndex = visibleTableColumns.findIndex( const currentColumnArrayIndex = visibleTableColumns.findIndex(
(visibleColumn) => (visibleColumn) =>
visibleColumn.fieldMetadataId === column.fieldMetadataId, visibleColumn.fieldMetadataId === column.fieldMetadataId,
@ -119,7 +126,12 @@ export const useTableColumns = (props?: useRecordTableProps) => {
await handleColumnsChange(columns); await handleColumnsChange(columns);
}, },
[visibleTableColumns, handleColumnMove, handleColumnsChange], [
unfocusRecordTableCell,
visibleTableColumns,
handleColumnMove,
handleColumnsChange,
],
); );
const handleColumnReorder = useCallback( const handleColumnReorder = useCallback(

View File

@ -1,11 +1,11 @@
import { Key } from 'ts-key-enum'; 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 { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; 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 { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { isAtLeastOneTableRowSelectedSelector } from '@/object-record/record-table/record-table-row/states/isAtLeastOneTableRowSelectedSelector'; 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'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const RecordTableBodyEscapeHotkeyEffect = () => { export const RecordTableBodyEscapeHotkeyEffect = () => {
@ -15,23 +15,26 @@ export const RecordTableBodyEscapeHotkeyEffect = () => {
recordTableId, recordTableId,
}); });
const { unfocusRecordTableRow } = useFocusedRecordTableRow(recordTableId);
const isAtLeastOneRecordSelected = useRecoilComponentValueV2( const isAtLeastOneRecordSelected = useRecoilComponentValueV2(
isAtLeastOneTableRowSelectedSelector, isAtLeastOneTableRowSelectedSelector,
); );
useScopedHotkeys( const handleEscape = () => {
[Key.Escape], if (isAtLeastOneRecordSelected) {
() => { resetTableRowSelection();
unfocusRecordTableRow(); }
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; return null;
}; };

View File

@ -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 { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useLeaveTableFocus } from '@/object-record/record-table/hooks/internal/useLeaveTableFocus'; 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 { 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 { 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 { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
@ -24,8 +22,6 @@ export const RecordTableBodyFocusClickOutsideEffect = ({
const currentHotkeyScope = useRecoilValue(currentHotkeyScopeState); const currentHotkeyScope = useRecoilValue(currentHotkeyScopeState);
const setHotkeyScope = useSetHotkeyScope();
useListenClickOutside({ useListenClickOutside({
excludedClickOutsideIds: [ excludedClickOutsideIds: [
ACTION_MENU_DROPDOWN_CLICK_OUTSIDE_ID, ACTION_MENU_DROPDOWN_CLICK_OUTSIDE_ID,
@ -36,19 +32,11 @@ export const RecordTableBodyFocusClickOutsideEffect = ({
listenerId: RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID, listenerId: RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID,
refs: [tableBodyRef], refs: [tableBodyRef],
callback: () => { callback: () => {
if ( if (currentHotkeyScope.scope !== RecordIndexHotkeyScope.RecordIndex) {
currentHotkeyScope.scope !== TableHotkeyScope.TableFocus &&
currentHotkeyScope.scope !== RecordIndexHotkeyScope.RecordIndex
) {
return; return;
} }
leaveTableFocus(); leaveTableFocus();
setHotkeyScope(RecordIndexHotkeyScope.RecordIndex, {
goto: true,
keyboardShortcutMenu: true,
searchRecords: true,
});
}, },
}); });

View File

@ -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 { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableRowFocusHotkeys } from '@/object-record/record-table/hooks/useRecordTableRowFocusHotkeys';
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';
export const RecordTableBodyFocusKeyboardEffect = () => { export const RecordTableBodyFocusKeyboardEffect = () => {
const { recordTableId } = useRecordTableContextOrThrow(); useRecordTableRowFocusHotkeys({
focusId: RECORD_INDEX_FOCUS_ID,
hotkeyScope: RecordIndexHotkeyScope.RecordIndex,
});
const setHotkeyScope = useSetHotkeyScope(); return null;
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 <></>;
}; };

View File

@ -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 <></>;
};

View File

@ -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;
};

View File

@ -5,7 +5,7 @@ import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/r
import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState'; import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState';
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex'; import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; 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 { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -93,7 +93,7 @@ export const RecordTableCellEditMode = ({
const { cellPosition } = useContext(RecordTableCellContext); const { cellPosition } = useContext(RecordTableCellContext);
const setFocusPosition = useSetRecordTableFocusPosition(); const { focusRecordTableCell } = useFocusRecordTableCell();
return ( return (
<StyledEditableCellEditModeContainer <StyledEditableCellEditModeContainer
@ -104,7 +104,7 @@ export const RecordTableCellEditMode = ({
{isFieldInputOnly ? ( {isFieldInputOnly ? (
<StyledInputModeOnlyContainer <StyledInputModeOnlyContainer
onClick={() => { onClick={() => {
setFocusPosition(cellPosition); focusRecordTableCell(cellPosition);
}} }}
> >
{children} {children}

View File

@ -1,9 +1,9 @@
import { RecordTableCellPortalWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellPortalWrapper'; import { RecordTableCellPortalWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellPortalWrapper';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; 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 { RecordTableCellEditMode } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditMode';
import { RecordTableCellFieldInput } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldInput'; 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 { recordTableCellEditModePositionComponentState } from '@/object-record/record-table/states/recordTableCellEditModePositionComponentState';
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState'; import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
@ -38,7 +38,7 @@ export const RecordTableCellEditModePortal = () => {
</RecordTableCellEditMode> </RecordTableCellEditMode>
</StyledRecordTableCellHoveredPortal> </StyledRecordTableCellHoveredPortal>
)} )}
<RecordTableFocusModeHotkeysSetterEffect /> <RecordTableCellHotkeysEffect />
</RecordTableCellPortalWrapper> </RecordTableCellPortalWrapper>
); );
}; };

View File

@ -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;
};

View File

@ -1,4 +1,5 @@
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; 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 { RecordTableCellEditModePortal } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditModePortal';
import { RecordTableCellHoveredPortal } from '@/object-record/record-table/record-table-cell/components/RecordTableCellHoveredPortal'; import { RecordTableCellHoveredPortal } from '@/object-record/record-table/record-table-cell/components/RecordTableCellHoveredPortal';
import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState'; import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState';
@ -16,7 +17,12 @@ export const RecordTableCellPortals = () => {
<> <>
<RecordTableCellHoveredPortal /> <RecordTableCellHoveredPortal />
{isRecordTableFocusActive && <RecordTableCellEditModePortal />} {isRecordTableFocusActive && (
<>
<RecordTableCellEditModePortal />
<RecordTableCellArrowKeysEffect />
</>
)}
</> </>
); );
}; };

View File

@ -2,6 +2,7 @@ import { renderHook } from '@testing-library/react';
import { act } from 'react'; import { act } from 'react';
import { RecoilRoot, useRecoilValue } from 'recoil'; import { RecoilRoot, useRecoilValue } from 'recoil';
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext'; 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'; } from '@/object-record/record-table/record-table-cell/hooks/__mocks__/cell';
import { useMoveHoverToCurrentCell } from '@/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell'; import { useMoveHoverToCurrentCell } from '@/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell';
import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState'; 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'; import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
const Wrapper = ({ children }: { children: React.ReactNode }) => ( const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot <RecoilRoot
initializeState={({ set }) => { initializeState={({ set }) => {
set(currentHotkeyScopeState, { set(currentHotkeyScopeState, {
scope: TableHotkeyScope.TableFocus, scope: RecordIndexHotkeyScope.RecordIndex,
customScopes: {}, customScopes: {},
}); });
}} }}

View File

@ -3,7 +3,7 @@ import React, { act } from 'react';
import { RecoilRoot, useRecoilValue } from 'recoil'; import { RecoilRoot, useRecoilValue } from 'recoil';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; 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 { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState';
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState'; import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition'; import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
@ -47,8 +47,8 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
const renderHooks = () => { const renderHooks = () => {
const { result } = renderHook( const { result } = renderHook(
() => { () => {
const { setIsFocusActive, setIsFocusActiveForCurrentPosition } = const { setIsRecordTableCellFocusActive } =
useSetIsRecordTableFocusActive('test-table-id'); useSetIsRecordTableCellFocusActive('test-table-id');
const isRecordTableFocusActive = useRecoilValue( const isRecordTableFocusActive = useRecoilValue(
isRecordTableCellFocusActiveComponentState.atomFamily({ isRecordTableCellFocusActiveComponentState.atomFamily({
instanceId: 'test-table-id', instanceId: 'test-table-id',
@ -60,8 +60,7 @@ const renderHooks = () => {
}), }),
); );
return { return {
setIsFocusActive, setIsRecordTableCellFocusActive,
setIsFocusActiveForCurrentPosition,
isRecordTableFocusActive, isRecordTableFocusActive,
focusPosition, focusPosition,
}; };
@ -87,7 +86,10 @@ describe('useSetIsRecordTableFocusActive', () => {
const cellPosition: TableCellPosition = { column: 1, row: 0 }; const cellPosition: TableCellPosition = { column: 1, row: 0 };
act(() => { act(() => {
result.current.setIsFocusActive(true, cellPosition); result.current.setIsRecordTableCellFocusActive({
isRecordTableFocusActive: true,
cellPosition,
});
}); });
expect(mockGetElementById).toHaveBeenCalledWith('record-table-cell-1-0'); expect(mockGetElementById).toHaveBeenCalledWith('record-table-cell-1-0');
@ -105,7 +107,10 @@ describe('useSetIsRecordTableFocusActive', () => {
const cellPosition: TableCellPosition = { column: 1, row: 0 }; const cellPosition: TableCellPosition = { column: 1, row: 0 };
act(() => { act(() => {
result.current.setIsFocusActive(false, cellPosition); result.current.setIsRecordTableCellFocusActive({
isRecordTableFocusActive: false,
cellPosition,
});
}); });
expect(mockGetElementById).toHaveBeenCalledWith('record-table-cell-1-0'); expect(mockGetElementById).toHaveBeenCalledWith('record-table-cell-1-0');
@ -117,22 +122,6 @@ describe('useSetIsRecordTableFocusActive', () => {
expect(result.current.focusPosition).toEqual(cellPosition); 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', () => { it('should handle case when the cell element is not found', () => {
mockGetElementById.mockReturnValue(null); mockGetElementById.mockReturnValue(null);
@ -141,7 +130,10 @@ describe('useSetIsRecordTableFocusActive', () => {
const cellPosition: TableCellPosition = { column: 1, row: 0 }; const cellPosition: TableCellPosition = { column: 1, row: 0 };
act(() => { act(() => {
result.current.setIsFocusActive(true, cellPosition); result.current.setIsRecordTableCellFocusActive({
isRecordTableFocusActive: true,
cellPosition,
});
}); });
expect(mockGetElementById).toHaveBeenCalledWith('record-table-cell-1-0'); expect(mockGetElementById).toHaveBeenCalledWith('record-table-cell-1-0');

View File

@ -21,12 +21,6 @@ import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; 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 onColumnsChange = jest.fn();
const recordTableId = 'scopeId'; const recordTableId = 'scopeId';
@ -94,10 +88,5 @@ describe('useCloseRecordTableCellInGroup', () => {
expect(result.current.isDragSelectionStartEnabled()).toBe(true); expect(result.current.isDragSelectionStartEnabled()).toBe(true);
expect(result.current.currentTableCellInEditModePosition).toBe(null); expect(result.current.currentTableCellInEditModePosition).toBe(null);
expect(setHotkeyScope).toHaveBeenCalledWith('table-focus', {
goto: true,
keyboardShortcutMenu: true,
searchRecords: true,
});
}); });
}); });

View File

@ -21,12 +21,6 @@ import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; 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 onColumnsChange = jest.fn();
const recordTableId = 'scopeId'; const recordTableId = 'scopeId';
@ -95,10 +89,5 @@ describe('useCloseRecordTableCellNoGroup', () => {
expect(result.current.isDragSelectionStartEnabled()).toBe(true); expect(result.current.isDragSelectionStartEnabled()).toBe(true);
expect(result.current.currentTableCellInEditModePosition).toBe(null); expect(result.current.currentTableCellInEditModePosition).toBe(null);
expect(setHotkeyScope).toHaveBeenCalledWith('table-focus', {
goto: true,
keyboardShortcutMenu: true,
searchRecords: true,
});
}); });
}); });

View File

@ -1,19 +1,18 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/FocusClickOutsideListenerId'; 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 { 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 { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode'; import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { clickOutsideListenerIsActivatedComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerIsActivatedComponentState';
import { useResetFocusStack } from '@/ui/utilities/focus/hooks/useResetFocusStack'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
export const useCloseRecordTableCellInGroup = () => { export const useCloseRecordTableCellInGroup = () => {
const { recordTableId } = useRecordTableContextOrThrow(); const { recordTableId } = useRecordTableContextOrThrow();
const setHotkeyScope = useSetHotkeyScope();
const { setDragSelectionStartEnabled } = useDragSelect(); const { setDragSelectionStartEnabled } = useDragSelect();
const { toggleClickOutside } = useClickOutsideListener( const { toggleClickOutside } = useClickOutsideListener(
@ -23,28 +22,24 @@ export const useCloseRecordTableCellInGroup = () => {
const closeCurrentTableCellInEditMode = const closeCurrentTableCellInEditMode =
useCloseCurrentTableCellInEditMode(recordTableId); useCloseCurrentTableCellInEditMode(recordTableId);
const { resetFocusStack } = useResetFocusStack(); const clickOutsideListenerIsActivatedState =
useRecoilComponentCallbackStateV2(
clickOutsideListenerIsActivatedComponentState,
RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID,
);
const closeTableCellInGroup = useRecoilCallback( const closeTableCellInGroup = useRecoilCallback(
() => () => { ({ set }) =>
toggleClickOutside(true); () => {
setDragSelectionStartEnabled(true); toggleClickOutside(true);
closeCurrentTableCellInEditMode(); setDragSelectionStartEnabled(true);
closeCurrentTableCellInEditMode();
// TODO: Remove this once we've fully migrated away from hotkey scopes set(clickOutsideListenerIsActivatedState, true);
resetFocusStack(); },
setHotkeyScope(TableHotkeyScope.TableFocus, {
goto: true,
keyboardShortcutMenu: true,
searchRecords: true,
});
},
[ [
clickOutsideListenerIsActivatedState,
closeCurrentTableCellInEditMode, closeCurrentTableCellInEditMode,
resetFocusStack,
setDragSelectionStartEnabled, setDragSelectionStartEnabled,
setHotkeyScope,
toggleClickOutside, toggleClickOutside,
], ],
); );

View File

@ -1,19 +1,17 @@
import { FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/FocusClickOutsideListenerId'; import { FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/FocusClickOutsideListenerId';
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; 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 { 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 { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode'; import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { clickOutsideListenerIsActivatedComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerIsActivatedComponentState';
import { useResetFocusStack } from '@/ui/utilities/focus/hooks/useResetFocusStack'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useCallback } from 'react'; import { useRecoilCallback } from 'recoil';
export const useCloseRecordTableCellNoGroup = () => { export const useCloseRecordTableCellNoGroup = () => {
const { recordTableId } = useRecordTableContextOrThrow(); const { recordTableId } = useRecordTableContextOrThrow();
const setHotkeyScope = useSetHotkeyScope();
const { setDragSelectionStartEnabled } = useDragSelect(); const { setDragSelectionStartEnabled } = useDragSelect();
const { toggleClickOutside } = useClickOutsideListener( const { toggleClickOutside } = useClickOutsideListener(
@ -23,28 +21,27 @@ export const useCloseRecordTableCellNoGroup = () => {
const closeCurrentTableCellInEditMode = const closeCurrentTableCellInEditMode =
useCloseCurrentTableCellInEditMode(recordTableId); useCloseCurrentTableCellInEditMode(recordTableId);
const { resetFocusStack } = useResetFocusStack(); const clickOutsideListenerIsActivatedState =
useRecoilComponentCallbackStateV2(
clickOutsideListenerIsActivatedComponentState,
RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID,
);
const closeTableCellNoGroup = useCallback(() => { const closeTableCellNoGroup = useRecoilCallback(
toggleClickOutside(true); ({ set }) =>
setDragSelectionStartEnabled(true); () => {
closeCurrentTableCellInEditMode(); toggleClickOutside(true);
setDragSelectionStartEnabled(true);
// TODO: Remove this once we've fully migrated away from hotkey scopes closeCurrentTableCellInEditMode();
resetFocusStack(); set(clickOutsideListenerIsActivatedState, true);
},
setHotkeyScope(TableHotkeyScope.TableFocus, { [
goto: true, clickOutsideListenerIsActivatedState,
keyboardShortcutMenu: true, closeCurrentTableCellInEditMode,
searchRecords: true, setDragSelectionStartEnabled,
}); toggleClickOutside,
}, [ ],
closeCurrentTableCellInEditMode, );
resetFocusStack,
setDragSelectionStartEnabled,
setHotkeyScope,
toggleClickOutside,
]);
return { return {
closeTableCellNoGroup, closeTableCellNoGroup,

View File

@ -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,
});
};

View File

@ -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 };
};

View File

@ -37,7 +37,6 @@ export const useMoveHoverToCurrentCell = (recordTableId: string) => {
); );
if ( if (
currentHotkeyScope.scope !== TableHotkeyScope.TableFocus &&
currentHotkeyScope.scope !== TableHotkeyScope.CellEditMode && currentHotkeyScope.scope !== TableHotkeyScope.CellEditMode &&
currentHotkeyScope.scope !== AppHotkeyScope.CommandMenuOpen && currentHotkeyScope.scope !== AppHotkeyScope.CommandMenuOpen &&
currentHotkeyScope.scope !== RecordIndexHotkeyScope.RecordIndex currentHotkeyScope.scope !== RecordIndexHotkeyScope.RecordIndex

View File

@ -24,9 +24,9 @@ import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInput
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView'; 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 { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow';
import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; 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 { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
import { clickOutsideListenerIsActivatedComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerIsActivatedComponentState'; import { clickOutsideListenerIsActivatedComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerIsActivatedComponentState';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
@ -90,7 +90,7 @@ export const useOpenRecordTableCellV2 = (recordTableId: string) => {
recordTableId, recordTableId,
); );
const setFocusPosition = useSetRecordTableFocusPosition(); const { focusRecordTableCell } = useFocusRecordTableCell();
const { openRecordFromIndexView } = useOpenRecordFromIndexView(); const { openRecordFromIndexView } = useOpenRecordFromIndexView();
@ -157,7 +157,7 @@ export const useOpenRecordTableCellV2 = (recordTableId: string) => {
deactivateRecordTableRow(); deactivateRecordTableRow();
setFocusPosition(cellPosition); focusRecordTableCell(cellPosition);
setIsRowFocusActive(false); setIsRowFocusActive(false);
@ -194,7 +194,7 @@ export const useOpenRecordTableCellV2 = (recordTableId: string) => {
[ [
clickOutsideListenerIsActivatedState, clickOutsideListenerIsActivatedState,
deactivateRecordTableRow, deactivateRecordTableRow,
setFocusPosition, focusRecordTableCell,
setIsRowFocusActive, setIsRowFocusActive,
setDragSelectionStartEnabled, setDragSelectionStartEnabled,
openFieldInput, openFieldInput,

View File

@ -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],
});
};

View File

@ -1,23 +1,23 @@
import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState'; 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 { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
export const useSetIsRecordTableFocusActive = (recordTableId?: string) => { export const useSetIsRecordTableCellFocusActive = (recordTableId?: string) => {
const isRecordTableFocusActiveState = useRecoilComponentCallbackStateV2( const isRecordTableFocusActiveState = useRecoilComponentCallbackStateV2(
isRecordTableCellFocusActiveComponentState, isRecordTableCellFocusActiveComponentState,
recordTableId, recordTableId,
); );
const focusPositionState = useRecoilComponentCallbackStateV2( const setIsRecordTableCellFocusActive = useRecoilCallback(
recordTableFocusPositionComponentState,
recordTableId,
);
const setIsFocusActive = useRecoilCallback(
({ set }) => ({ set }) =>
(isRecordTableFocusActive: boolean, cellPosition: TableCellPosition) => { ({
isRecordTableFocusActive,
cellPosition,
}: {
isRecordTableFocusActive: boolean;
cellPosition: TableCellPosition;
}) => {
const cellId = `record-table-cell-${cellPosition.column}-${cellPosition.row}`; const cellId = `record-table-cell-${cellPosition.column}-${cellPosition.row}`;
const cellElement = document.getElementById(cellId); const cellElement = document.getElementById(cellId);
@ -35,17 +35,7 @@ export const useSetIsRecordTableFocusActive = (recordTableId?: string) => {
[isRecordTableFocusActiveState], [isRecordTableFocusActiveState],
); );
const setIsFocusActiveForCurrentPosition = useRecoilCallback( return {
({ snapshot }) => setIsRecordTableCellFocusActive,
(isRecordTableFocusActive: boolean) => { };
const currentPosition = snapshot
.getLoadable(focusPositionState)
.getValue();
setIsFocusActive(isRecordTableFocusActive, currentPosition);
},
[setIsFocusActive, focusPositionState],
);
return { setIsFocusActive, setIsFocusActiveForCurrentPosition };
}; };

View File

@ -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 };
};

View File

@ -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}`;
};

View File

@ -2,7 +2,7 @@ import { useCreateEmptyRecordFilterFromFieldMetadataItem } from '@/object-record
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext'; import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter'; import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; 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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetEditableFilterChipDropdownStates } from '@/views/hooks/useSetEditableFilterChipDropdownStates'; import { useSetEditableFilterChipDropdownStates } from '@/views/hooks/useSetEditableFilterChipDropdownStates';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
@ -20,7 +20,7 @@ export const useOpenRecordFilterChipFromTableHeader = () => {
const { upsertRecordFilter } = useUpsertRecordFilter(); const { upsertRecordFilter } = useUpsertRecordFilter();
const { openDropdownFromOutside } = useOpenDropdownFromOutside(); const { openDropdown } = useDropdownV2();
const { setEditableFilterChipDropdownStates } = const { setEditableFilterChipDropdownStates } =
useSetEditableFilterChipDropdownStates(); useSetEditableFilterChipDropdownStates();
@ -45,7 +45,7 @@ export const useOpenRecordFilterChipFromTableHeader = () => {
if (isDefined(existingNonAdvancedRecordFilter)) { if (isDefined(existingNonAdvancedRecordFilter)) {
setEditableFilterChipDropdownStates(existingNonAdvancedRecordFilter); setEditableFilterChipDropdownStates(existingNonAdvancedRecordFilter);
openDropdownFromOutside(existingNonAdvancedRecordFilter.id); openDropdown(existingNonAdvancedRecordFilter.id);
return; return;
} }
@ -56,7 +56,7 @@ export const useOpenRecordFilterChipFromTableHeader = () => {
upsertRecordFilter(newRecordFilter); upsertRecordFilter(newRecordFilter);
setEditableFilterChipDropdownStates(newRecordFilter); setEditableFilterChipDropdownStates(newRecordFilter);
openDropdownFromOutside(newRecordFilter.id); openDropdown(newRecordFilter.id);
}; };
return { openRecordFilterChipFromTableHeader }; return { openRecordFilterChipFromTableHeader };

View File

@ -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 { RecordTableLastEmptyCell } from '@/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell';
import { RecordTableCells } from '@/object-record/record-table/record-table-row/components/RecordTableCells'; 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 { 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 { RecordTableRowHotkeyEffect } from '@/object-record/record-table/record-table-row/components/RecordTableRowHotkeyEffect';
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState'; import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState'; import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
@ -41,7 +42,12 @@ export const RecordTableRow = ({
draggableIndex={rowIndexForDrag} draggableIndex={rowIndexForDrag}
focusIndex={rowIndexForFocus} focusIndex={rowIndexForFocus}
> >
{isRowFocusActive && isFocused && <RecordTableRowHotkeyEffect />} {isRowFocusActive && isFocused && (
<>
<RecordTableRowHotkeyEffect />
<RecordTableRowArrowKeysEffect />
</>
)}
<RecordTableCellGrip /> <RecordTableCellGrip />
<RecordTableCellCheckbox /> <RecordTableCellCheckbox />
<RecordTableCells /> <RecordTableCells />

View File

@ -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;
};

View File

@ -1,76 +1,10 @@
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu'; import { useRecordTableRowFocusId } from '@/object-record/record-table/record-table-row/hooks/useRecordTableRowFocusId';
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { useRecordTableRowHotkeys } from '@/object-record/record-table/record-table-row/hooks/useRecordTableRowHotkeys';
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';
export const RecordTableRowHotkeyEffect = () => { export const RecordTableRowHotkeyEffect = () => {
const { isSelected, recordId, objectNameSingular, rowIndex } = const recordTableRowFocusId = useRecordTableRowFocusId();
useRecordTableRowContextOrThrow();
const { setCurrentRowSelected } = useSetCurrentRowSelected(); useRecordTableRowHotkeys(recordTableRowFocusId);
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,
);
return null; return null;
}; };

View File

@ -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,
});
};

View File

@ -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],
});
};

View File

@ -0,0 +1,9 @@
export const getRecordTableRowFocusId = ({
recordTableId,
rowIndex,
}: {
recordTableId: string;
rowIndex: number;
}) => {
return `${recordTableId}-row-${rowIndex}`;
};

View File

@ -1,6 +1,3 @@
export enum TableHotkeyScope { export enum TableHotkeyScope {
CellDoubleTextInput = 'cell-double-text-input',
CellEditMode = 'cell-edit-mode', CellEditMode = 'cell-edit-mode',
CellDateEditMode = 'cell-date-edit-mode',
TableFocus = 'table-focus',
} }

View File

@ -395,6 +395,7 @@ const initializeModalState = ({ set }: { set: SetRecoilState }) => {
enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true, enableGlobalHotkeysConflictingWithKeyboard: true,
}, },
memoizeKey: 'global',
}, },
]); ]);
}; };

View File

@ -1,14 +1,15 @@
import { useCloseDropdownFromOutside } from '@/ui/layout/dropdown/hooks/useCloseDropdownFromOutside'; import { useCloseDropdownFromOutside } from '@/ui/layout/dropdown/hooks/useCloseDropdownFromOutside';
import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState'; import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState';
import { previousDropdownFocusIdState } from '@/ui/layout/dropdown/states/previousDropdownFocusIdState'; 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 { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
export const useCloseAnyOpenDropdown = () => { export const useCloseAnyOpenDropdown = () => {
const { closeDropdownFromOutside } = useCloseDropdownFromOutside(); const { closeDropdownFromOutside } = useCloseDropdownFromOutside();
const { removeFocusItemFromFocusStack } = useRemoveFocusItemFromFocusStack(); const { removeFocusItemFromFocusStackById } =
useRemoveFocusItemFromFocusStackById();
const closeAnyOpenDropdown = useRecoilCallback( const closeAnyOpenDropdown = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
@ -33,24 +34,22 @@ export const useCloseAnyOpenDropdown = () => {
if (isDefined(activeDropdownFocusId)) { if (isDefined(activeDropdownFocusId)) {
closeDropdownFromOutside(activeDropdownFocusId); closeDropdownFromOutside(activeDropdownFocusId);
removeFocusItemFromFocusStack({ removeFocusItemFromFocusStackById({
focusId: activeDropdownFocusId, focusId: activeDropdownFocusId,
memoizeKey: 'global',
}); });
} }
if (thereIsOneNestedDropdownOpen) { if (thereIsOneNestedDropdownOpen) {
closeDropdownFromOutside(previousDropdownFocusId); closeDropdownFromOutside(previousDropdownFocusId);
removeFocusItemFromFocusStack({ removeFocusItemFromFocusStackById({
focusId: previousDropdownFocusId, focusId: previousDropdownFocusId,
memoizeKey: 'global',
}); });
} }
set(previousDropdownFocusIdState, null); set(previousDropdownFocusIdState, null);
set(activeDropdownFocusIdState, null); set(activeDropdownFocusIdState, null);
}, },
[closeDropdownFromOutside, removeFocusItemFromFocusStack], [closeDropdownFromOutside, removeFocusItemFromFocusStackById],
); );
return { closeAnyOpenDropdown }; return { closeAnyOpenDropdown };

View File

@ -4,7 +4,7 @@ import { useDropdownStates } from '@/ui/layout/dropdown/hooks/internal/useDropdo
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId'; import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; 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 { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { GlobalHotkeysConfig } from '@/ui/utilities/hotkey/types/GlobalHotkeysConfig'; import { GlobalHotkeysConfig } from '@/ui/utilities/hotkey/types/GlobalHotkeysConfig';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
@ -12,7 +12,8 @@ import { useCallback } from 'react';
export const useDropdown = (dropdownId?: string) => { export const useDropdown = (dropdownId?: string) => {
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const { removeFocusItemFromFocusStack } = useRemoveFocusItemFromFocusStack(); const { removeFocusItemFromFocusStackById } =
useRemoveFocusItemFromFocusStackById();
const { scopeId, isDropdownOpenState, dropdownPlacementState } = const { scopeId, isDropdownOpenState, dropdownPlacementState } =
useDropdownStates({ dropdownScopeId: dropdownId }); useDropdownStates({ dropdownScopeId: dropdownId });
@ -34,16 +35,15 @@ export const useDropdown = (dropdownId?: string) => {
if (isDropdownOpen) { if (isDropdownOpen) {
setIsDropdownOpen(false); setIsDropdownOpen(false);
goBackToPreviousDropdownFocusId(); goBackToPreviousDropdownFocusId();
removeFocusItemFromFocusStack({ removeFocusItemFromFocusStackById({
focusId: dropdownId ?? scopeId, focusId: dropdownId ?? scopeId,
memoizeKey: 'global',
}); });
} }
}, [ }, [
isDropdownOpen, isDropdownOpen,
setIsDropdownOpen, setIsDropdownOpen,
goBackToPreviousDropdownFocusId, goBackToPreviousDropdownFocusId,
removeFocusItemFromFocusStack, removeFocusItemFromFocusStackById,
dropdownId, dropdownId,
scopeId, scopeId,
]); ]);
@ -63,7 +63,6 @@ export const useDropdown = (dropdownId?: string) => {
globalHotkeysConfig, globalHotkeysConfig,
// TODO: Remove this once we've fully migrated away from hotkey scopes // TODO: Remove this once we've fully migrated away from hotkey scopes
hotkeyScope: { scope: 'dropdown' } as HotkeyScope, hotkeyScope: { scope: 'dropdown' } as HotkeyScope,
memoizeKey: 'global',
}); });
} }
}, },

View File

@ -4,7 +4,7 @@ import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/u
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; 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 { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { GlobalHotkeysConfig } from '@/ui/utilities/hotkey/types/GlobalHotkeysConfig'; import { GlobalHotkeysConfig } from '@/ui/utilities/hotkey/types/GlobalHotkeysConfig';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
@ -15,7 +15,8 @@ export const useDropdownV2 = () => {
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const { removeFocusItemFromFocusStack } = useRemoveFocusItemFromFocusStack(); const { removeFocusItemFromFocusStackById } =
useRemoveFocusItemFromFocusStackById();
const { setActiveDropdownFocusIdAndMemorizePrevious } = const { setActiveDropdownFocusIdAndMemorizePrevious } =
useSetActiveDropdownFocusIdAndMemorizePrevious(); useSetActiveDropdownFocusIdAndMemorizePrevious();
@ -30,9 +31,8 @@ export const useDropdownV2 = () => {
.getValue(); .getValue();
if (isDropdownOpen) { if (isDropdownOpen) {
removeFocusItemFromFocusStack({ removeFocusItemFromFocusStackById({
focusId: scopeId, focusId: scopeId,
memoizeKey: 'global',
}); });
goBackToPreviousDropdownFocusId(); goBackToPreviousDropdownFocusId();
set( set(
@ -43,7 +43,7 @@ export const useDropdownV2 = () => {
); );
} }
}, },
[removeFocusItemFromFocusStack, goBackToPreviousDropdownFocusId], [removeFocusItemFromFocusStackById, goBackToPreviousDropdownFocusId],
); );
const openDropdown = useRecoilCallback( const openDropdown = useRecoilCallback(

View File

@ -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 };
};

View File

@ -44,6 +44,7 @@ const initializeState = ({ set }: { set: SetRecoilState }) => {
enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true, enableGlobalHotkeysConflictingWithKeyboard: true,
}, },
memoizeKey: 'global',
}, },
]); ]);
}; };

View File

@ -44,6 +44,7 @@ const initializeState = ({ set }: { set: SetRecoilState }) => {
enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true, enableGlobalHotkeysConflictingWithKeyboard: true,
}, },
memoizeKey: 'global',
}, },
]); ]);
}; };

View File

@ -1,13 +1,14 @@
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope'; import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState'; import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; 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 { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
export const useModal = () => { export const useModal = () => {
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const { removeFocusItemFromFocusStack } = useRemoveFocusItemFromFocusStack(); const { removeFocusItemFromFocusStackById } =
useRemoveFocusItemFromFocusStackById();
const closeModal = useRecoilCallback( const closeModal = useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
@ -22,9 +23,8 @@ export const useModal = () => {
return; return;
} }
removeFocusItemFromFocusStack({ removeFocusItemFromFocusStackById({
focusId: modalId, focusId: modalId,
memoizeKey: modalId,
}); });
set( set(
@ -32,7 +32,7 @@ export const useModal = () => {
false, false,
); );
}, },
[removeFocusItemFromFocusStack], [removeFocusItemFromFocusStackById],
); );
const openModal = useRecoilCallback( const openModal = useRecoilCallback(

View File

@ -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]);
};

View File

@ -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]);
};

View File

@ -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]);
};

View File

@ -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));
};

View File

@ -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));
};

View File

@ -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));
};

View File

@ -43,6 +43,7 @@ describe('usePushFocusItemToFocusStack', () => {
enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true, enableGlobalHotkeysConflictingWithKeyboard: true,
}, },
memoizeKey: 'global',
}; };
await act(async () => { await act(async () => {
@ -70,6 +71,7 @@ describe('usePushFocusItemToFocusStack', () => {
enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true, enableGlobalHotkeysConflictingWithKeyboard: true,
}, },
memoizeKey: 'global',
}; };
await act(async () => { await act(async () => {

View File

@ -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);
});
});

View File

@ -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);
});
});

View File

@ -44,6 +44,7 @@ describe('useResetFocusStack', () => {
enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true, enableGlobalHotkeysConflictingWithKeyboard: true,
}, },
memoizeKey: 'global',
}; };
await act(async () => { await act(async () => {

View File

@ -44,6 +44,7 @@ describe('useResetFocusStackToFocusItem', () => {
enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true, enableGlobalHotkeysConflictingWithKeyboard: true,
}, },
memoizeKey: 'global',
}; };
const secondFocusItem = { const secondFocusItem = {
@ -56,6 +57,7 @@ describe('useResetFocusStackToFocusItem', () => {
enableGlobalHotkeysWithModifiers: true, enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true, enableGlobalHotkeysConflictingWithKeyboard: true,
}, },
memoizeKey: 'global',
}; };
await act(async () => { await act(async () => {
@ -66,7 +68,7 @@ describe('useResetFocusStackToFocusItem', () => {
instanceId: firstFocusItem.componentInstance.componentInstanceId, instanceId: firstFocusItem.componentInstance.componentInstanceId,
}, },
hotkeyScope: { scope: 'test-scope' }, hotkeyScope: { scope: 'test-scope' },
memoizeKey: 'global', memoizeKey: firstFocusItem.memoizeKey,
}); });
}); });
@ -78,7 +80,7 @@ describe('useResetFocusStackToFocusItem', () => {
instanceId: secondFocusItem.componentInstance.componentInstanceId, instanceId: secondFocusItem.componentInstance.componentInstanceId,
}, },
hotkeyScope: { scope: 'test-scope' }, hotkeyScope: { scope: 'test-scope' },
memoizeKey: 'global', memoizeKey: secondFocusItem.memoizeKey,
}); });
}); });
@ -92,7 +94,7 @@ describe('useResetFocusStackToFocusItem', () => {
result.current.resetFocusStackToFocusItem({ result.current.resetFocusStackToFocusItem({
focusStackItem: firstFocusItem, focusStackItem: firstFocusItem,
hotkeyScope: { scope: 'test-scope' }, hotkeyScope: { scope: 'test-scope' },
memoizeKey: 'global', memoizeKey: firstFocusItem.memoizeKey,
}); });
}); });

View File

@ -42,7 +42,7 @@ export const usePushFocusItemToFocusStack = () => {
globalHotkeysConfig?: Partial<GlobalHotkeysConfig>; globalHotkeysConfig?: Partial<GlobalHotkeysConfig>;
// TODO: Remove this once we've migrated hotkey scopes to the new api // TODO: Remove this once we've migrated hotkey scopes to the new api
hotkeyScope: HotkeyScope; hotkeyScope: HotkeyScope;
memoizeKey: string; memoizeKey?: string;
}) => { }) => {
const focusStackItem: FocusStackItem = { const focusStackItem: FocusStackItem = {
focusId, focusId,
@ -57,6 +57,8 @@ export const usePushFocusItemToFocusStack = () => {
globalHotkeysConfig?.enableGlobalHotkeysConflictingWithKeyboard ?? globalHotkeysConfig?.enableGlobalHotkeysConflictingWithKeyboard ??
true, true,
}, },
// TODO: Remove this once we've migrated hotkey scopes to the new api
memoizeKey,
}; };
const currentFocusStack = snapshot const currentFocusStack = snapshot

View File

@ -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 };
};

View File

@ -4,14 +4,22 @@ import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousH
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { logDebug } from '~/utils/logDebug'; import { logDebug } from '~/utils/logDebug';
export const useRemoveFocusItemFromFocusStack = () => { export const useRemoveFocusItemFromFocusStackById = () => {
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope(); const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
const removeFocusItemFromFocusStack = useRecoilCallback( const removeFocusItemFromFocusStackById = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
({ focusId, memoizeKey }: { focusId: string; memoizeKey: string }) => { ({ focusId }: { focusId: string }) => {
const focusStack = snapshot.getLoadable(focusStackState).getValue(); const focusStack = snapshot.getLoadable(focusStackState).getValue();
const removedFocusItem = focusStack.find(
(focusStackItem) => focusStackItem.focusId === focusId,
);
if (!removedFocusItem) {
return;
}
const newFocusStack = focusStack.filter( const newFocusStack = focusStack.filter(
(focusStackItem) => focusStackItem.focusId !== focusId, (focusStackItem) => focusStackItem.focusId !== focusId,
); );
@ -25,10 +33,10 @@ export const useRemoveFocusItemFromFocusStack = () => {
} }
// TODO: Remove this once we've migrated hotkey scopes to the new api // TODO: Remove this once we've migrated hotkey scopes to the new api
goBackToPreviousHotkeyScope(memoizeKey); goBackToPreviousHotkeyScope(removedFocusItem.memoizeKey);
}, },
[goBackToPreviousHotkeyScope], [goBackToPreviousHotkeyScope],
); );
return { removeFocusItemFromFocusStack }; return { removeFocusItemFromFocusStackById };
}; };

View File

@ -1,23 +1,25 @@
import { DEBUG_FOCUS_STACK } from '@/ui/utilities/focus/constants/DebugFocusStack'; import { DEBUG_FOCUS_STACK } from '@/ui/utilities/focus/constants/DebugFocusStack';
import { focusStackState } from '@/ui/utilities/focus/states/focusStackState'; import { focusStackState } from '@/ui/utilities/focus/states/focusStackState';
import { FocusStackItem } from '@/ui/utilities/focus/types/FocusStackItem'; 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 { previousHotkeyScopeFamilyState } from '@/ui/utilities/hotkey/states/internal/previousHotkeyScopeFamilyState';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { logDebug } from '~/utils/logDebug'; import { logDebug } from '~/utils/logDebug';
export const useResetFocusStackToFocusItem = () => { export const useResetFocusStackToFocusItem = () => {
const setHotkeyScope = useSetHotkeyScope();
const resetFocusStackToFocusItem = useRecoilCallback( const resetFocusStackToFocusItem = useRecoilCallback(
({ set }) => ({ set }) =>
({ ({
focusStackItem, focusStackItem,
hotkeyScope, hotkeyScope,
memoizeKey, memoizeKey = 'global',
}: { }: {
focusStackItem: FocusStackItem; focusStackItem: FocusStackItem;
hotkeyScope: HotkeyScope; hotkeyScope: HotkeyScope;
memoizeKey: string; memoizeKey?: string;
}) => { }) => {
set(focusStackState, [focusStackItem]); set(focusStackState, [focusStackItem]);
@ -29,9 +31,9 @@ export const useResetFocusStackToFocusItem = () => {
// TODO: Remove this once we've migrated hotkey scopes to the new api // TODO: Remove this once we've migrated hotkey scopes to the new api
set(previousHotkeyScopeFamilyState(memoizeKey), null); set(previousHotkeyScopeFamilyState(memoizeKey), null);
set(currentHotkeyScopeState, hotkeyScope); setHotkeyScope(hotkeyScope.scope, hotkeyScope.customScopes);
}, },
[], [setHotkeyScope],
); );
return { resetFocusStackToFocusItem }; return { resetFocusStackToFocusItem };

View File

@ -3,4 +3,8 @@ export enum FocusComponentType {
DROPDOWN = 'dropdown', DROPDOWN = 'dropdown',
SIDE_PANEL = 'side-panel', SIDE_PANEL = 'side-panel',
OPEN_FIELD_INPUT = 'open-field-input', OPEN_FIELD_INPUT = 'open-field-input',
PAGE = 'page',
RECORD_TABLE = 'record-table',
RECORD_TABLE_ROW = 'record-table-row',
RECORD_TABLE_CELL = 'record-table-cell',
} }

View File

@ -5,4 +5,5 @@ export type FocusStackItem = {
focusId: string; focusId: string;
componentInstance: FocusComponentInstance; componentInstance: FocusComponentInstance;
globalHotkeysConfig: GlobalHotkeysConfig; globalHotkeysConfig: GlobalHotkeysConfig;
memoizeKey: string;
}; };

View File

@ -1,6 +1,6 @@
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant'; 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 { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -180,7 +180,7 @@ export const WorkflowDiagramCanvasBase = ({
}); });
}; };
useListenRightDrawerClose(() => { useListenToSidePanelClosing(() => {
reactflow.setNodes((nodes) => reactflow.setNodes((nodes) =>
nodes.map((node) => ({ ...node, selected: false })), nodes.map((node) => ({ ...node, selected: false })),
); );