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:
@ -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(() => {
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export const RECORD_INDEX_FOCUS_ID = 'record-index';
|
||||||
@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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 />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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 <></>;
|
|
||||||
};
|
|
||||||
@ -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();
|
||||||
|
|||||||
@ -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();
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -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],
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -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,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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],
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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],
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -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(
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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 <></>;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 <></>;
|
|
||||||
};
|
|
||||||
@ -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;
|
||||||
|
};
|
||||||
@ -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}
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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;
|
||||||
|
};
|
||||||
@ -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 />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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: {},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
@ -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,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -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 };
|
||||||
|
};
|
||||||
@ -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
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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],
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -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 };
|
|
||||||
};
|
};
|
||||||
@ -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 };
|
||||||
|
};
|
||||||
@ -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}`;
|
||||||
|
};
|
||||||
@ -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 };
|
||||||
|
|||||||
@ -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 />
|
||||||
|
|||||||
@ -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;
|
||||||
|
};
|
||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -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],
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
export const getRecordTableRowFocusId = ({
|
||||||
|
recordTableId,
|
||||||
|
rowIndex,
|
||||||
|
}: {
|
||||||
|
recordTableId: string;
|
||||||
|
rowIndex: number;
|
||||||
|
}) => {
|
||||||
|
return `${recordTableId}-row-${rowIndex}`;
|
||||||
|
};
|
||||||
@ -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',
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -395,6 +395,7 @@ const initializeModalState = ({ set }: { set: SetRecoilState }) => {
|
|||||||
enableGlobalHotkeysWithModifiers: true,
|
enableGlobalHotkeysWithModifiers: true,
|
||||||
enableGlobalHotkeysConflictingWithKeyboard: true,
|
enableGlobalHotkeysConflictingWithKeyboard: true,
|
||||||
},
|
},
|
||||||
|
memoizeKey: 'global',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 };
|
||||||
|
|||||||
@ -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',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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 };
|
|
||||||
};
|
|
||||||
@ -44,6 +44,7 @@ const initializeState = ({ set }: { set: SetRecoilState }) => {
|
|||||||
enableGlobalHotkeysWithModifiers: true,
|
enableGlobalHotkeysWithModifiers: true,
|
||||||
enableGlobalHotkeysConflictingWithKeyboard: true,
|
enableGlobalHotkeysConflictingWithKeyboard: true,
|
||||||
},
|
},
|
||||||
|
memoizeKey: 'global',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -44,6 +44,7 @@ const initializeState = ({ set }: { set: SetRecoilState }) => {
|
|||||||
enableGlobalHotkeysWithModifiers: true,
|
enableGlobalHotkeysWithModifiers: true,
|
||||||
enableGlobalHotkeysConflictingWithKeyboard: true,
|
enableGlobalHotkeysConflictingWithKeyboard: true,
|
||||||
},
|
},
|
||||||
|
memoizeKey: 'global',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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]);
|
|
||||||
};
|
|
||||||
@ -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]);
|
||||||
|
};
|
||||||
@ -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]);
|
||||||
|
};
|
||||||
@ -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));
|
|
||||||
};
|
|
||||||
@ -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));
|
||||||
|
};
|
||||||
@ -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));
|
||||||
|
};
|
||||||
@ -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 () => {
|
||||||
|
|||||||
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -44,6 +44,7 @@ describe('useResetFocusStack', () => {
|
|||||||
enableGlobalHotkeysWithModifiers: true,
|
enableGlobalHotkeysWithModifiers: true,
|
||||||
enableGlobalHotkeysConflictingWithKeyboard: true,
|
enableGlobalHotkeysConflictingWithKeyboard: true,
|
||||||
},
|
},
|
||||||
|
memoizeKey: 'global',
|
||||||
};
|
};
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 };
|
||||||
|
};
|
||||||
@ -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 };
|
||||||
};
|
};
|
||||||
@ -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 };
|
||||||
|
|||||||
@ -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',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,4 +5,5 @@ export type FocusStackItem = {
|
|||||||
focusId: string;
|
focusId: string;
|
||||||
componentInstance: FocusComponentInstance;
|
componentInstance: FocusComponentInstance;
|
||||||
globalHotkeysConfig: GlobalHotkeysConfig;
|
globalHotkeysConfig: GlobalHotkeysConfig;
|
||||||
|
memoizeKey: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 })),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user