Replace hotkey scopes by focus stack (Part 3 - Record Board, Cards and Inline Cells) (#12910)
# Replace hotkey scopes by focus stack (Part 3 - Record Board, Cards and Inline 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 Part 2: https://github.com/twentyhq/twenty/pull/12798 The board shortcuts are no longer centralized in the record board, they are now split and the focused element is in charge of applying the desired shortcuts. ## Video QA: https://github.com/user-attachments/assets/20ba4a24-6fc3-4a97-9cd3-68e846699e30
This commit is contained in:
@ -5,17 +5,17 @@ import { RecordBoardContext } from '@/object-record/record-board/contexts/Record
|
||||
import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard';
|
||||
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
|
||||
import { recordBoardSelectedRecordIdsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardSelectedRecordIdsComponentSelector';
|
||||
import { BoardHotkeyScope } from '@/object-record/record-board/types/BoardHotkeyScope';
|
||||
import { useResetFocusStackToRecordIndex } from '@/object-record/record-index/hooks/useResetFocusStackToRecordIndex';
|
||||
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
export const RecordBoardBodyEscapeHotkeyEffect = () => {
|
||||
const { recordBoardId } = useContext(RecordBoardContext);
|
||||
|
||||
const { resetRecordSelection } = useRecordBoardSelection(recordBoardId);
|
||||
|
||||
const { unfocusBoardCard } = useFocusedRecordBoardCard(recordBoardId);
|
||||
const { resetFocusStackToRecordIndex } = useResetFocusStackToRecordIndex();
|
||||
|
||||
const selectedRecordIds = useRecoilComponentValueV2(
|
||||
recordBoardSelectedRecordIdsComponentSelector,
|
||||
@ -24,29 +24,21 @@ export const RecordBoardBodyEscapeHotkeyEffect = () => {
|
||||
|
||||
const isAtLeastOneRecordSelected = selectedRecordIds.length > 0;
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
unfocusBoardCard();
|
||||
if (isAtLeastOneRecordSelected) {
|
||||
resetRecordSelection();
|
||||
}
|
||||
},
|
||||
RecordIndexHotkeyScope.RecordIndex,
|
||||
[isAtLeastOneRecordSelected, resetRecordSelection, unfocusBoardCard],
|
||||
);
|
||||
const handleEscape = () => {
|
||||
unfocusBoardCard();
|
||||
if (isAtLeastOneRecordSelected) {
|
||||
resetRecordSelection();
|
||||
}
|
||||
resetFocusStackToRecordIndex();
|
||||
};
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
unfocusBoardCard();
|
||||
if (isAtLeastOneRecordSelected) {
|
||||
resetRecordSelection();
|
||||
}
|
||||
},
|
||||
BoardHotkeyScope.BoardFocus,
|
||||
[isAtLeastOneRecordSelected, resetRecordSelection, unfocusBoardCard],
|
||||
);
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [Key.Escape],
|
||||
callback: handleEscape,
|
||||
focusId: recordBoardId,
|
||||
scope: RecordIndexHotkeyScope.RecordIndex,
|
||||
dependencies: [handleEscape],
|
||||
});
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@ -3,137 +3,30 @@ import { Key } from 'ts-key-enum';
|
||||
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { useRecordBoardCardNavigation } from '@/object-record/record-board/hooks/useRecordBoardCardNavigation';
|
||||
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
|
||||
import { BoardHotkeyScope } from '@/object-record/record-board/types/BoardHotkeyScope';
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
import { useRecordBoardSelectAllHotkeys } from '@/object-record/record-board/hooks/useRecordBoardSelectAllHotkeys';
|
||||
import { RECORD_INDEX_FOCUS_ID } from '@/object-record/record-index/constants/RecordIndexFocusId';
|
||||
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
const BOARD_NAVIGATION_CUSTOM_SCOPES = {
|
||||
goto: true,
|
||||
keyboardShortcutMenu: true,
|
||||
searchRecords: true,
|
||||
};
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
|
||||
export const RecordBoardHotkeyEffect = () => {
|
||||
const { recordBoardId } = useContext(RecordBoardContext);
|
||||
|
||||
const { move } = useRecordBoardCardNavigation(recordBoardId);
|
||||
|
||||
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
|
||||
|
||||
const recordIndexAllRecordIdsState = useRecoilComponentCallbackStateV2(
|
||||
recordIndexAllRecordIdsComponentSelector,
|
||||
);
|
||||
|
||||
const { setRecordAsSelected } = useRecordBoardSelection(recordBoardId);
|
||||
|
||||
const selectAll = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const allRecordIds = getSnapshotValue(
|
||||
snapshot,
|
||||
recordIndexAllRecordIdsState,
|
||||
);
|
||||
|
||||
for (const recordId of allRecordIds) {
|
||||
setRecordAsSelected(recordId, true);
|
||||
}
|
||||
},
|
||||
[recordIndexAllRecordIdsState, setRecordAsSelected],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
'ctrl+a,meta+a',
|
||||
selectAll,
|
||||
RecordIndexHotkeyScope.RecordIndex,
|
||||
);
|
||||
|
||||
useScopedHotkeys('ctrl+a,meta+a', selectAll, BoardHotkeyScope.BoardFocus);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.ArrowLeft,
|
||||
() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: BoardHotkeyScope.BoardFocus,
|
||||
customScopes: BOARD_NAVIGATION_CUSTOM_SCOPES,
|
||||
});
|
||||
move('left');
|
||||
},
|
||||
RecordIndexHotkeyScope.RecordIndex,
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.ArrowRight,
|
||||
() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: BoardHotkeyScope.BoardFocus,
|
||||
customScopes: BOARD_NAVIGATION_CUSTOM_SCOPES,
|
||||
});
|
||||
move('right');
|
||||
},
|
||||
RecordIndexHotkeyScope.RecordIndex,
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.ArrowUp,
|
||||
() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: BoardHotkeyScope.BoardFocus,
|
||||
customScopes: BOARD_NAVIGATION_CUSTOM_SCOPES,
|
||||
});
|
||||
move('up');
|
||||
},
|
||||
RecordIndexHotkeyScope.RecordIndex,
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.ArrowDown,
|
||||
() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope({
|
||||
scope: BoardHotkeyScope.BoardFocus,
|
||||
customScopes: BOARD_NAVIGATION_CUSTOM_SCOPES,
|
||||
});
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [Key.ArrowLeft, Key.ArrowUp, Key.ArrowDown, Key.ArrowRight],
|
||||
callback: () => {
|
||||
move('down');
|
||||
},
|
||||
RecordIndexHotkeyScope.RecordIndex,
|
||||
);
|
||||
focusId: RECORD_INDEX_FOCUS_ID,
|
||||
scope: RecordIndexHotkeyScope.RecordIndex,
|
||||
dependencies: [move],
|
||||
});
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.ArrowLeft,
|
||||
() => {
|
||||
move('left');
|
||||
},
|
||||
BoardHotkeyScope.BoardFocus,
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.ArrowRight,
|
||||
() => {
|
||||
move('right');
|
||||
},
|
||||
BoardHotkeyScope.BoardFocus,
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.ArrowUp,
|
||||
() => {
|
||||
move('up');
|
||||
},
|
||||
BoardHotkeyScope.BoardFocus,
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.ArrowDown,
|
||||
() => {
|
||||
move('down');
|
||||
},
|
||||
BoardHotkeyScope.BoardFocus,
|
||||
);
|
||||
useRecordBoardSelectAllHotkeys({
|
||||
recordBoardId,
|
||||
focusId: recordBoardId,
|
||||
});
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export const RECORD_BOARD_FOCUS_ID = 'record-board';
|
||||
@ -1,7 +1,12 @@
|
||||
import { getRecordBoardCardFocusId } from '@/object-record/record-board/record-board-card/utils/getRecordBoardCardFocusId';
|
||||
import { focusedRecordBoardCardIndexesComponentState } from '@/object-record/record-board/states/focusedRecordBoardCardIndexesComponentState';
|
||||
import { isRecordBoardCardFocusActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCardFocusActiveComponentState';
|
||||
import { isRecordBoardCardFocusedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardFocusedComponentFamilyState';
|
||||
import { BoardCardIndexes } from '@/object-record/record-board/types/BoardCardIndexes';
|
||||
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
|
||||
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 { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@ -22,6 +27,10 @@ export const useFocusedRecordBoardCard = (recordBoardId?: string) => {
|
||||
recordBoardId,
|
||||
);
|
||||
|
||||
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
|
||||
const { removeFocusItemFromFocusStackById } =
|
||||
useRemoveFocusItemFromFocusStackById();
|
||||
|
||||
const unfocusBoardCard = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
() => {
|
||||
@ -33,11 +42,26 @@ export const useFocusedRecordBoardCard = (recordBoardId?: string) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const focusId = getRecordBoardCardFocusId({
|
||||
recordBoardId: recordBoardId || '',
|
||||
cardIndexes: focusedBoardCardIndexes,
|
||||
});
|
||||
|
||||
removeFocusItemFromFocusStackById({
|
||||
focusId,
|
||||
});
|
||||
|
||||
set(focusedBoardCardIndexesState, null);
|
||||
set(isCardFocusedState(focusedBoardCardIndexes), false);
|
||||
set(isCardFocusActiveState, false);
|
||||
},
|
||||
[focusedBoardCardIndexesState, isCardFocusedState, isCardFocusActiveState],
|
||||
[
|
||||
focusedBoardCardIndexesState,
|
||||
isCardFocusedState,
|
||||
isCardFocusActiveState,
|
||||
recordBoardId,
|
||||
removeFocusItemFromFocusStackById,
|
||||
],
|
||||
);
|
||||
|
||||
const focusBoardCard = useRecoilCallback(
|
||||
@ -54,13 +78,51 @@ export const useFocusedRecordBoardCard = (recordBoardId?: string) => {
|
||||
boardCardIndexes.columnIndex)
|
||||
) {
|
||||
set(isCardFocusedState(focusedBoardCardIndexes), false);
|
||||
|
||||
const currentFocusId = getRecordBoardCardFocusId({
|
||||
recordBoardId: recordBoardId || '',
|
||||
cardIndexes: focusedBoardCardIndexes,
|
||||
});
|
||||
|
||||
removeFocusItemFromFocusStackById({
|
||||
focusId: currentFocusId,
|
||||
});
|
||||
}
|
||||
|
||||
const focusId = getRecordBoardCardFocusId({
|
||||
recordBoardId: recordBoardId || '',
|
||||
cardIndexes: boardCardIndexes,
|
||||
});
|
||||
|
||||
pushFocusItemToFocusStack({
|
||||
focusId,
|
||||
component: {
|
||||
type: FocusComponentType.RECORD_BOARD_CARD,
|
||||
instanceId: focusId,
|
||||
},
|
||||
hotkeyScope: {
|
||||
scope: RecordIndexHotkeyScope.RecordIndex,
|
||||
customScopes: {
|
||||
goto: true,
|
||||
keyboardShortcutMenu: true,
|
||||
searchRecords: true,
|
||||
},
|
||||
},
|
||||
memoizeKey: focusId,
|
||||
});
|
||||
|
||||
set(focusedBoardCardIndexesState, boardCardIndexes);
|
||||
set(isCardFocusedState(boardCardIndexes), true);
|
||||
set(isCardFocusActiveState, true);
|
||||
},
|
||||
[focusedBoardCardIndexesState, isCardFocusedState, isCardFocusActiveState],
|
||||
[
|
||||
focusedBoardCardIndexesState,
|
||||
isCardFocusedState,
|
||||
isCardFocusActiveState,
|
||||
recordBoardId,
|
||||
pushFocusItemToFocusStack,
|
||||
removeFocusItemFromFocusStackById,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
import { useRecordBoardCardNavigation } from '@/object-record/record-board/hooks/useRecordBoardCardNavigation';
|
||||
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
export const useRecordBoardArrowKeysEffect = ({
|
||||
recordBoardId,
|
||||
focusId,
|
||||
}: {
|
||||
recordBoardId: string;
|
||||
focusId: string;
|
||||
}) => {
|
||||
const { move } = useRecordBoardCardNavigation(recordBoardId);
|
||||
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [Key.ArrowLeft],
|
||||
callback: () => move('left'),
|
||||
focusId,
|
||||
scope: RecordIndexHotkeyScope.RecordIndex,
|
||||
dependencies: [move],
|
||||
});
|
||||
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [Key.ArrowRight],
|
||||
callback: () => move('right'),
|
||||
focusId,
|
||||
scope: RecordIndexHotkeyScope.RecordIndex,
|
||||
dependencies: [move],
|
||||
});
|
||||
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [Key.ArrowUp],
|
||||
callback: () => move('up'),
|
||||
focusId,
|
||||
scope: RecordIndexHotkeyScope.RecordIndex,
|
||||
dependencies: [move],
|
||||
});
|
||||
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [Key.ArrowDown],
|
||||
callback: () => move('down'),
|
||||
focusId,
|
||||
scope: RecordIndexHotkeyScope.RecordIndex,
|
||||
dependencies: [move],
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,91 @@
|
||||
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard';
|
||||
import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard';
|
||||
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
|
||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||
import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState';
|
||||
import { recordBoardSelectedRecordIdsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardSelectedRecordIdsComponentSelector';
|
||||
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useContext } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
export const useRecordBoardCardHotkeys = (focusId: string) => {
|
||||
const { objectMetadataItem, recordBoardId } = useContext(RecordBoardContext);
|
||||
const { recordId, rowIndex, columnIndex } = useContext(
|
||||
RecordBoardCardContext,
|
||||
);
|
||||
|
||||
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
|
||||
const { activateBoardCard } = useActiveRecordBoardCard();
|
||||
const { setRecordAsSelected, resetRecordSelection } =
|
||||
useRecordBoardSelection();
|
||||
const { unfocusBoardCard } = useFocusedRecordBoardCard(recordBoardId);
|
||||
|
||||
const isRecordBoardCardSelected = useRecoilComponentFamilyValueV2(
|
||||
isRecordBoardCardSelectedComponentFamilyState,
|
||||
recordId,
|
||||
);
|
||||
|
||||
const selectedRecordIds = useRecoilComponentValueV2(
|
||||
recordBoardSelectedRecordIdsComponentSelector,
|
||||
recordBoardId,
|
||||
);
|
||||
|
||||
const isAtLeastOneRecordSelected = selectedRecordIds.length > 0;
|
||||
|
||||
const handleSelectCard = () => {
|
||||
setRecordAsSelected(recordId, !isRecordBoardCardSelected);
|
||||
};
|
||||
|
||||
const handleOpenRecordInCommandMenu = () => {
|
||||
openRecordInCommandMenu({
|
||||
recordId,
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
isNewRecord: false,
|
||||
});
|
||||
|
||||
activateBoardCard({
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
});
|
||||
};
|
||||
|
||||
const handleEscape = () => {
|
||||
unfocusBoardCard();
|
||||
if (isAtLeastOneRecordSelected) {
|
||||
resetRecordSelection();
|
||||
}
|
||||
};
|
||||
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: ['x'],
|
||||
callback: handleSelectCard,
|
||||
focusId,
|
||||
scope: RecordIndexHotkeyScope.RecordIndex,
|
||||
dependencies: [handleSelectCard],
|
||||
});
|
||||
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [
|
||||
Key.Enter,
|
||||
`${Key.Control}+${Key.Enter}`,
|
||||
`${Key.Meta}+${Key.Enter}`,
|
||||
],
|
||||
callback: handleOpenRecordInCommandMenu,
|
||||
focusId,
|
||||
scope: RecordIndexHotkeyScope.RecordIndex,
|
||||
dependencies: [handleOpenRecordInCommandMenu],
|
||||
});
|
||||
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [Key.Escape],
|
||||
callback: handleEscape,
|
||||
focusId,
|
||||
scope: RecordIndexHotkeyScope.RecordIndex,
|
||||
dependencies: [handleEscape],
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,47 @@
|
||||
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
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 useRecordBoardSelectAllHotkeys = ({
|
||||
recordBoardId,
|
||||
focusId,
|
||||
}: {
|
||||
recordBoardId: string;
|
||||
focusId: string;
|
||||
}) => {
|
||||
const recordIndexAllRecordIdsState = useRecoilComponentCallbackStateV2(
|
||||
recordIndexAllRecordIdsComponentSelector,
|
||||
);
|
||||
|
||||
const { setRecordAsSelected } = useRecordBoardSelection(recordBoardId);
|
||||
|
||||
const selectAll = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const allRecordIds = getSnapshotValue(
|
||||
snapshot,
|
||||
recordIndexAllRecordIdsState,
|
||||
);
|
||||
|
||||
for (const recordId of allRecordIds) {
|
||||
setRecordAsSelected(recordId, true);
|
||||
}
|
||||
},
|
||||
[recordIndexAllRecordIdsState, setRecordAsSelected],
|
||||
);
|
||||
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: ['ctrl+a', 'meta+a'],
|
||||
callback: selectAll,
|
||||
focusId,
|
||||
scope: RecordIndexHotkeyScope.RecordIndex,
|
||||
dependencies: [selectAll],
|
||||
options: {
|
||||
enableOnFormTags: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -4,7 +4,7 @@ import { useContext } from 'react';
|
||||
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
|
||||
import { RecordBoardCardFocusHotkeyEffect } from '@/object-record/record-board/record-board-card/components/RecordBoardCardFocusHotkeyEffect';
|
||||
import { RecordBoardCardHotkeysEffect } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHotkeysEffect';
|
||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
||||
import { isRecordBoardCardFocusedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardFocusedComponentFamilyState';
|
||||
@ -57,9 +57,7 @@ export const RecordBoardCardDraggableContainer = ({
|
||||
data-selectable-id={recordId}
|
||||
data-select-disable
|
||||
>
|
||||
{isRecordBoardCardFocusActive && (
|
||||
<RecordBoardCardFocusHotkeyEffect />
|
||||
)}
|
||||
{isRecordBoardCardFocusActive && <RecordBoardCardHotkeysEffect />}
|
||||
<RecordBoardCard />
|
||||
</StyledDraggableContainer>
|
||||
)}
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
import { useContext } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard';
|
||||
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
|
||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||
import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState';
|
||||
import { BoardHotkeyScope } from '@/object-record/record-board/types/BoardHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
export const RecordBoardCardFocusHotkeyEffect = () => {
|
||||
const { objectMetadataItem } = useContext(RecordBoardContext);
|
||||
|
||||
const { recordId, rowIndex, columnIndex } = useContext(
|
||||
RecordBoardCardContext,
|
||||
);
|
||||
|
||||
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
|
||||
|
||||
const { activateBoardCard } = useActiveRecordBoardCard();
|
||||
|
||||
const { setRecordAsSelected } = useRecordBoardSelection();
|
||||
|
||||
const isRecordBoardCardSelected = useRecoilComponentFamilyValueV2(
|
||||
isRecordBoardCardSelectedComponentFamilyState,
|
||||
recordId,
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
'x',
|
||||
() => {
|
||||
setRecordAsSelected(recordId, !isRecordBoardCardSelected);
|
||||
},
|
||||
BoardHotkeyScope.BoardFocus,
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Enter, `${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`],
|
||||
() => {
|
||||
openRecordInCommandMenu({
|
||||
recordId: recordId,
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
isNewRecord: false,
|
||||
});
|
||||
|
||||
activateBoardCard({
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
});
|
||||
},
|
||||
BoardHotkeyScope.BoardFocus,
|
||||
);
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { useRecordBoardArrowKeysEffect } from '@/object-record/record-board/hooks/useRecordBoardArrowKeysEffect';
|
||||
import { useRecordBoardCardHotkeys } from '@/object-record/record-board/hooks/useRecordBoardCardHotkeys';
|
||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||
import { getRecordBoardCardFocusId } from '@/object-record/record-board/record-board-card/utils/getRecordBoardCardFocusId';
|
||||
|
||||
export const RecordBoardCardHotkeysEffect = () => {
|
||||
const { recordBoardId } = useContext(RecordBoardContext);
|
||||
const { rowIndex, columnIndex } = useContext(RecordBoardCardContext);
|
||||
|
||||
const focusId = getRecordBoardCardFocusId({
|
||||
recordBoardId,
|
||||
cardIndexes: { rowIndex, columnIndex },
|
||||
});
|
||||
|
||||
useRecordBoardCardHotkeys(focusId);
|
||||
useRecordBoardArrowKeysEffect({ recordBoardId, focusId });
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
import { BoardCardIndexes } from '@/object-record/record-board/types/BoardCardIndexes';
|
||||
|
||||
export const getRecordBoardCardFocusId = ({
|
||||
recordBoardId,
|
||||
cardIndexes,
|
||||
}: {
|
||||
recordBoardId: string;
|
||||
cardIndexes: BoardCardIndexes;
|
||||
}) => {
|
||||
return `${recordBoardId}-board-card-${cardIndexes.columnIndex}-${cardIndexes.rowIndex}`;
|
||||
};
|
||||
@ -7,7 +7,6 @@ import { getActivityTargetObjectRecords } from '@/activities/utils/getActivityTa
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useOpenRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useOpenRelationFromManyFieldInput';
|
||||
import { useOpenRelationToOneFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useOpenRelationToOneFieldInput';
|
||||
import { getRelationFromManyFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationFromManyFieldInputInstanceId';
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import {
|
||||
FieldMetadata,
|
||||
@ -22,6 +21,7 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco
|
||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
|
||||
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 { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@ -75,7 +75,7 @@ export const useOpenFieldInputEditMode = () => {
|
||||
});
|
||||
|
||||
openActivityTargetCellEditMode({
|
||||
recordPickerInstanceId: getRelationFromManyFieldInputInstanceId({
|
||||
recordPickerInstanceId: getFieldInputInstanceId({
|
||||
recordId,
|
||||
fieldName: fieldDefinition.metadata.fieldName,
|
||||
}),
|
||||
@ -110,16 +110,16 @@ export const useOpenFieldInputEditMode = () => {
|
||||
}
|
||||
|
||||
pushFocusItemToFocusStack({
|
||||
focusId: getFieldInputInstanceId(
|
||||
focusId: getFieldInputInstanceId({
|
||||
recordId,
|
||||
fieldDefinition.metadata.fieldName,
|
||||
),
|
||||
fieldName: fieldDefinition.metadata.fieldName,
|
||||
}),
|
||||
component: {
|
||||
type: FocusComponentType.OPEN_FIELD_INPUT,
|
||||
instanceId: getFieldInputInstanceId(
|
||||
type: FocusComponentType.OPENED_FIELD_INPUT,
|
||||
instanceId: getFieldInputInstanceId({
|
||||
recordId,
|
||||
fieldDefinition.metadata.fieldName,
|
||||
),
|
||||
fieldName: fieldDefinition.metadata.fieldName,
|
||||
}),
|
||||
},
|
||||
hotkeyScope: {
|
||||
scope: DEFAULT_CELL_SCOPE.scope,
|
||||
@ -136,8 +136,26 @@ export const useOpenFieldInputEditMode = () => {
|
||||
],
|
||||
);
|
||||
|
||||
const { removeFocusItemFromFocusStackById } =
|
||||
useRemoveFocusItemFromFocusStackById();
|
||||
|
||||
const closeFieldInput = ({
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
}: {
|
||||
fieldDefinition: FieldDefinition<FieldMetadata>;
|
||||
recordId: string;
|
||||
}) => {
|
||||
removeFocusItemFromFocusStackById({
|
||||
focusId: getFieldInputInstanceId({
|
||||
recordId,
|
||||
fieldName: fieldDefinition.metadata.fieldName,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
openFieldInput: openFieldInput,
|
||||
closeFieldInput: () => {},
|
||||
openFieldInput,
|
||||
closeFieldInput,
|
||||
};
|
||||
};
|
||||
|
||||
@ -18,10 +18,10 @@ export const MultiSelectFieldInput = ({
|
||||
selectableListComponentInstanceId={
|
||||
SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID
|
||||
}
|
||||
focusId={getFieldInputInstanceId(
|
||||
focusId={getFieldInputInstanceId({
|
||||
recordId,
|
||||
fieldDefinition.metadata.fieldName,
|
||||
)}
|
||||
fieldName: fieldDefinition.metadata.fieldName,
|
||||
})}
|
||||
options={fieldDefinition.metadata.options}
|
||||
onCancel={onCancel}
|
||||
onOptionSelected={persistField}
|
||||
|
||||
@ -10,11 +10,11 @@ import { FieldContext } from '@/object-record/record-field/contexts/FieldContext
|
||||
import { useRelationField } from '@/object-record/record-field/meta-types/hooks/useRelationField';
|
||||
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer';
|
||||
import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput';
|
||||
import { getRelationFromManyFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationFromManyFieldInputInstanceId';
|
||||
import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionComponentState';
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { getFieldInputInstanceId } from '@/object-record/record-field/utils/getFieldInputInstanceId';
|
||||
import { MultipleRecordPicker } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker';
|
||||
import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch';
|
||||
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
|
||||
@ -31,7 +31,7 @@ export const RelationFromManyFieldInput = ({
|
||||
onSubmit,
|
||||
}: RelationFromManyFieldInputProps) => {
|
||||
const { fieldDefinition, recordId } = useContext(FieldContext);
|
||||
const recordPickerInstanceId = getRelationFromManyFieldInputInstanceId({
|
||||
const recordPickerInstanceId = getFieldInputInstanceId({
|
||||
recordId,
|
||||
fieldName: fieldDefinition.metadata.fieldName,
|
||||
});
|
||||
|
||||
@ -3,10 +3,10 @@ import { useRelationField } from '../../hooks/useRelationField';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer';
|
||||
import { getRelationToOneFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationToOneFieldInputInstanceId';
|
||||
import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionComponentState';
|
||||
import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState';
|
||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||
import { getFieldInputInstanceId } from '@/object-record/record-field/utils/getFieldInputInstanceId';
|
||||
import { SingleRecordPicker } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPicker';
|
||||
import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState';
|
||||
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
|
||||
@ -29,7 +29,7 @@ export const RelationToOneFieldInput = ({
|
||||
|
||||
const persistField = usePersistField();
|
||||
|
||||
const recordPickerInstanceId = getRelationToOneFieldInputInstanceId({
|
||||
const recordPickerInstanceId = getFieldInputInstanceId({
|
||||
recordId,
|
||||
fieldName: fieldDefinition.metadata.fieldName,
|
||||
});
|
||||
|
||||
@ -67,10 +67,10 @@ export const SelectFieldInput = ({
|
||||
SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID
|
||||
}
|
||||
selectableItemIdArray={optionIds}
|
||||
focusId={getFieldInputInstanceId(
|
||||
focusId={getFieldInputInstanceId({
|
||||
recordId,
|
||||
fieldDefinition.metadata.fieldName,
|
||||
)}
|
||||
fieldName: fieldDefinition.metadata.fieldName,
|
||||
})}
|
||||
onEnter={(itemId) => {
|
||||
const option = filteredOptions.find(
|
||||
(option) => option.value === itemId,
|
||||
|
||||
@ -18,8 +18,8 @@ import {
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode';
|
||||
import { getRelationFromManyFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationFromManyFieldInputInstanceId';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||
import { getFieldInputInstanceId } from '@/object-record/record-field/utils/getFieldInputInstanceId';
|
||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
@ -88,7 +88,7 @@ const RelationManyFieldInputWithContext = () => {
|
||||
<div>
|
||||
<RecordFieldComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: getRelationFromManyFieldInputInstanceId({
|
||||
instanceId: getFieldInputInstanceId({
|
||||
recordId: 'recordId',
|
||||
fieldName: 'people',
|
||||
}),
|
||||
|
||||
@ -16,9 +16,9 @@ import {
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { getRelationToOneFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationToOneFieldInputInstanceId';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||
import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState';
|
||||
import { getFieldInputInstanceId } from '@/object-record/record-field/utils/getFieldInputInstanceId';
|
||||
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
|
||||
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
|
||||
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||
@ -68,13 +68,13 @@ const RelationToOneFieldInputWithContext = ({
|
||||
|
||||
useEffect(() => {
|
||||
pushFocusItemToFocusStack({
|
||||
focusId: getRelationToOneFieldInputInstanceId({
|
||||
focusId: getFieldInputInstanceId({
|
||||
recordId: '123',
|
||||
fieldName: 'Relation',
|
||||
}),
|
||||
component: {
|
||||
type: FocusComponentType.DROPDOWN,
|
||||
instanceId: getRelationToOneFieldInputInstanceId({
|
||||
instanceId: getFieldInputInstanceId({
|
||||
recordId: '123',
|
||||
fieldName: 'Relation',
|
||||
}),
|
||||
@ -82,7 +82,7 @@ const RelationToOneFieldInputWithContext = ({
|
||||
hotkeyScope: {
|
||||
scope: DropdownHotkeyScope.Dropdown,
|
||||
},
|
||||
memoizeKey: getRelationToOneFieldInputInstanceId({
|
||||
memoizeKey: getFieldInputInstanceId({
|
||||
recordId: '123',
|
||||
fieldName: 'Relation',
|
||||
}),
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { getRelationFromManyFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationFromManyFieldInputInstanceId';
|
||||
import {
|
||||
FieldRelationFromManyValue,
|
||||
FieldRelationValue,
|
||||
} from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { getFieldInputInstanceId } from '@/object-record/record-field/utils/getFieldInputInstanceId';
|
||||
import { useMultipleRecordPickerOpen } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerOpen';
|
||||
import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch';
|
||||
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
|
||||
@ -33,7 +33,7 @@ export const useOpenRelationFromManyFieldInput = () => {
|
||||
objectNameSingular: string;
|
||||
recordId: string;
|
||||
}) => {
|
||||
const recordPickerInstanceId = getRelationFromManyFieldInputInstanceId({
|
||||
const recordPickerInstanceId = getFieldInputInstanceId({
|
||||
recordId,
|
||||
fieldName,
|
||||
});
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { getRelationToOneFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationToOneFieldInputInstanceId';
|
||||
import {
|
||||
FieldRelationToOneValue,
|
||||
FieldRelationValue,
|
||||
} from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { getFieldInputInstanceId } from '@/object-record/record-field/utils/getFieldInputInstanceId';
|
||||
import { useSingleRecordPickerOpen } from '@/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerOpen';
|
||||
import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState';
|
||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||
@ -19,7 +19,7 @@ export const useOpenRelationToOneFieldInput = () => {
|
||||
const openRelationToOneFieldInput = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
({ fieldName, recordId }: { fieldName: string; recordId: string }) => {
|
||||
const recordPickerInstanceId = getRelationToOneFieldInputInstanceId({
|
||||
const recordPickerInstanceId = getFieldInputInstanceId({
|
||||
recordId,
|
||||
fieldName,
|
||||
});
|
||||
@ -46,7 +46,7 @@ export const useOpenRelationToOneFieldInput = () => {
|
||||
pushFocusItemToFocusStack({
|
||||
focusId: recordPickerInstanceId,
|
||||
component: {
|
||||
type: FocusComponentType.OPEN_FIELD_INPUT,
|
||||
type: FocusComponentType.OPENED_FIELD_INPUT,
|
||||
instanceId: recordPickerInstanceId,
|
||||
},
|
||||
// TODO: Remove this once we've fully migrated away from hotkey scopes
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
export const getRelationFromManyFieldInputInstanceId = ({
|
||||
recordId,
|
||||
fieldName,
|
||||
}: {
|
||||
recordId: string;
|
||||
fieldName: string;
|
||||
}): string => {
|
||||
return `relation-from-many-field-input-${recordId}-${fieldName}`;
|
||||
};
|
||||
@ -1,9 +0,0 @@
|
||||
export const getRelationToOneFieldInputInstanceId = ({
|
||||
recordId,
|
||||
fieldName,
|
||||
}: {
|
||||
recordId: string;
|
||||
fieldName: string;
|
||||
}): string => {
|
||||
return `relation-to-one-field-input-${recordId}-${fieldName}`;
|
||||
};
|
||||
@ -1,6 +1,9 @@
|
||||
export const getFieldInputInstanceId = (
|
||||
recordId: string,
|
||||
fieldName: string,
|
||||
) => {
|
||||
export const getFieldInputInstanceId = ({
|
||||
recordId,
|
||||
fieldName,
|
||||
}: {
|
||||
recordId: string;
|
||||
fieldName: string;
|
||||
}) => {
|
||||
return `${recordId}-${fieldName}`;
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useContext } from 'react';
|
||||
import { useCallback, useContext } from 'react';
|
||||
|
||||
import { FieldDisplay } from '@/object-record/record-field/components/FieldDisplay';
|
||||
import { FieldInput } from '@/object-record/record-field/components/FieldInput';
|
||||
@ -13,10 +13,13 @@ import {
|
||||
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
||||
import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode';
|
||||
|
||||
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||
import { isInlineCellInEditModeScopedState } from '@/object-record/record-inline-cell/states/isInlineCellInEditModeScopedState';
|
||||
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
|
||||
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
|
||||
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { RecordInlineCellContainer } from './RecordInlineCellContainer';
|
||||
import {
|
||||
@ -35,16 +38,47 @@ export const RecordInlineCell = ({ loading }: RecordInlineCellProps) => {
|
||||
recordId,
|
||||
isCentered,
|
||||
isDisplayModeFixHeight,
|
||||
onOpenEditMode,
|
||||
onCloseEditMode,
|
||||
onOpenEditMode: onOpenEditModeFromContext,
|
||||
onCloseEditMode: onCloseEditModeFromContext,
|
||||
isReadOnly,
|
||||
} = useContext(FieldContext);
|
||||
|
||||
const { openFieldInput, closeFieldInput } = useOpenFieldInputEditMode();
|
||||
|
||||
const onOpenEditMode = onOpenEditModeFromContext
|
||||
? onOpenEditModeFromContext
|
||||
: () => openFieldInput({ fieldDefinition, recordId });
|
||||
|
||||
const onCloseEditMode = useCallback(() => {
|
||||
onCloseEditModeFromContext
|
||||
? onCloseEditModeFromContext()
|
||||
: closeFieldInput({ fieldDefinition, recordId });
|
||||
}, [onCloseEditModeFromContext, closeFieldInput, fieldDefinition, recordId]);
|
||||
|
||||
const buttonIcon = useGetButtonIcon();
|
||||
|
||||
const isFieldInputOnly = useIsFieldInputOnly();
|
||||
|
||||
const { closeInlineCell } = useInlineCell();
|
||||
const { goBackToPreviousDropdownFocusId } =
|
||||
useGoBackToPreviousDropdownFocusId();
|
||||
|
||||
const recordFieldComponentInstanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
RecordFieldComponentInstanceContext,
|
||||
);
|
||||
|
||||
const setIsInlineCellInEditMode = useSetRecoilState(
|
||||
isInlineCellInEditModeScopedState(recordFieldComponentInstanceId),
|
||||
);
|
||||
|
||||
const closeInlineCell = useCallback(() => {
|
||||
onCloseEditMode();
|
||||
setIsInlineCellInEditMode(false);
|
||||
goBackToPreviousDropdownFocusId();
|
||||
}, [
|
||||
onCloseEditMode,
|
||||
setIsInlineCellInEditMode,
|
||||
goBackToPreviousDropdownFocusId,
|
||||
]);
|
||||
|
||||
const handleEnter: FieldInputEvent = (persistField) => {
|
||||
persistField();
|
||||
@ -94,7 +128,6 @@ export const RecordInlineCell = ({ loading }: RecordInlineCellProps) => {
|
||||
);
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
const { openFieldInput, closeFieldInput } = useOpenFieldInputEditMode();
|
||||
|
||||
const RecordInlineCellContextValue: RecordInlineCellContextProps = {
|
||||
readonly: isReadOnly,
|
||||
@ -122,9 +155,8 @@ export const RecordInlineCell = ({ loading }: RecordInlineCellProps) => {
|
||||
isDisplayModeFixHeight: isDisplayModeFixHeight,
|
||||
editModeContentOnly: isFieldInputOnly,
|
||||
loading: loading,
|
||||
onOpenEditMode:
|
||||
onOpenEditMode ?? (() => openFieldInput({ fieldDefinition, recordId })),
|
||||
onCloseEditMode: onCloseEditMode ?? (() => closeFieldInput()),
|
||||
onOpenEditMode,
|
||||
onCloseEditMode,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
||||
import { useListenToSidePanelOpening } from '@/ui/layout/right-drawer/hooks/useListenToSidePanelOpening';
|
||||
|
||||
// TODO: This is a temporary solution to close the inline cell when the command menu is opened.
|
||||
// This is because the useInlineCell hook doesn't work correctly for field inputs when used outside of the field context.
|
||||
// We should refactor field inputs, and remove this listener afterwards.
|
||||
// TODO: create a new hook useCloseAnyOpenedFieldInput which uses the focus stack to close all the field inputs, and call it inside openCommandMenu
|
||||
export const RecordInlineCellCloseOnCommandMenuOpeningEffect = () => {
|
||||
const { closeInlineCell } = useInlineCell();
|
||||
|
||||
useListenToSidePanelOpening(closeInlineCell);
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -9,6 +9,7 @@ import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInput
|
||||
|
||||
import { assertFieldMetadata } from '@/object-record/record-field/types/guards/assertFieldMetadata';
|
||||
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
|
||||
import { RecordInlineCellCloseOnCommandMenuOpeningEffect } from '@/object-record/record-inline-cell/components/RecordInlineCellCloseOnCommandMenuOpeningEffect';
|
||||
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
||||
import {
|
||||
AppTooltip,
|
||||
@ -160,6 +161,9 @@ export const RecordInlineCellContainer = () => {
|
||||
)}
|
||||
</StyledLabelAndIconContainer>
|
||||
)}
|
||||
{isInlineCellInEditMode && (
|
||||
<RecordInlineCellCloseOnCommandMenuOpeningEffect />
|
||||
)}
|
||||
<StyledValueContainer readonly={readonly ?? false}>
|
||||
<RecordInlineCellValue />
|
||||
</StyledValueContainer>
|
||||
|
||||
@ -2,12 +2,10 @@ import { useContext } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
|
||||
import { useInitDraftValueV2 } from '@/object-record/record-field/hooks/useInitDraftValueV2';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||
import { useRecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext';
|
||||
import { INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY } from '@/object-record/record-inline-cell/constants/InlineCellHotkeyScopeMemoizeKey';
|
||||
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
|
||||
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
|
||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||
@ -35,16 +33,12 @@ export const useInlineCell = (
|
||||
const { goBackToPreviousDropdownFocusId } =
|
||||
useGoBackToPreviousDropdownFocusId();
|
||||
|
||||
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
|
||||
|
||||
const initFieldInputDraftValue = useInitDraftValueV2();
|
||||
|
||||
const closeInlineCell = () => {
|
||||
onCloseEditMode?.();
|
||||
setIsInlineCellInEditMode(false);
|
||||
|
||||
goBackToPreviousHotkeyScope(INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY);
|
||||
|
||||
goBackToPreviousDropdownFocusId();
|
||||
};
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ export const useCloseCurrentTableCellInEditMode = (recordTableId?: string) => {
|
||||
goBackToPreviousDropdownFocusId();
|
||||
|
||||
removeLastFocusItemFromFocusStackByComponentType({
|
||||
componentType: FocusComponentType.OPEN_FIELD_INPUT,
|
||||
componentType: FocusComponentType.OPENED_FIELD_INPUT,
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
@ -2,9 +2,11 @@ export enum FocusComponentType {
|
||||
MODAL = 'modal',
|
||||
DROPDOWN = 'dropdown',
|
||||
SIDE_PANEL = 'side-panel',
|
||||
OPEN_FIELD_INPUT = 'open-field-input',
|
||||
OPENED_FIELD_INPUT = 'opened-field-input',
|
||||
PAGE = 'page',
|
||||
RECORD_TABLE = 'record-table',
|
||||
RECORD_TABLE_ROW = 'record-table-row',
|
||||
RECORD_TABLE_CELL = 'record-table-cell',
|
||||
RECORD_BOARD = 'record-board',
|
||||
RECORD_BOARD_CARD = 'record-board-card',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user