Fixed (#11482)
This PR fixes many small bugs around the recent hotkey scope refactor. - Removed unused ActionBar files - Created components CommandMenuOpenContainer and KeyboardShortcutMenuOpenContent to avoid mounting listeners when not needed - Added DEFAULT_CELL_SCOPE where missing in some field inputs - Called setHotkeyScopeAndMemorizePreviousScope instead of setHotkeyScope in new useOpenFieldInputEditMode hook - Broke down RecordTableBodyUnselectEffect into multiple simpler effect components that are mounted only when needed to avoid listening for keyboard and clickoutside event - Re-implemented recently deleted table cell soft focus component logic into RecordTableCellDisplayMode - Created component selector isAtLeastOneTableRowSelectedSelector - Drill down hotkey scope when opening a dropdown - Improved debug logs
This commit is contained in:
@ -1,9 +0,0 @@
|
|||||||
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
|
|
||||||
|
|
||||||
describe('getActionBarIdFromActionMenuId', () => {
|
|
||||||
it('should return the correct action bar id', () => {
|
|
||||||
expect(getActionBarIdFromActionMenuId('action-menu-id')).toBe(
|
|
||||||
'action-bar-action-menu-id',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
export const getActionBarIdFromActionMenuId = (actionMenuId: string) => {
|
|
||||||
return `action-bar-${actionMenuId}`;
|
|
||||||
};
|
|
||||||
@ -1,12 +1,8 @@
|
|||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
import { COMMAND_MENU_ANIMATION_VARIANTS } from '@/command-menu/constants/CommandMenuAnimationVariants';
|
import { CommandMenuOpenContainer } from '@/command-menu/components/CommandMenuOpenContainer';
|
||||||
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
|
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
|
||||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
|
||||||
import { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup';
|
import { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup';
|
||||||
import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
|
|
||||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||||
import { CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant';
|
|
||||||
import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeyScope';
|
|
||||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||||
@ -15,78 +11,20 @@ import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/reco
|
|||||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||||
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
|
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
|
||||||
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
|
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
|
||||||
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
|
|
||||||
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useTheme } from '@emotion/react';
|
import { AnimatePresence } from 'framer-motion';
|
||||||
import styled from '@emotion/styled';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
|
||||||
import { useRef } from 'react';
|
|
||||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
|
||||||
import { useIsMobile } from 'twenty-ui/utilities';
|
|
||||||
|
|
||||||
const StyledCommandMenu = styled(motion.div)`
|
|
||||||
background: ${({ theme }) => theme.background.primary};
|
|
||||||
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
|
|
||||||
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
|
||||||
font-family: ${({ theme }) => theme.font.family};
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 0;
|
|
||||||
position: fixed;
|
|
||||||
right: 0%;
|
|
||||||
top: 0%;
|
|
||||||
z-index: ${RootStackingContextZIndices.CommandMenu};
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const CommandMenuContainer = ({
|
export const CommandMenuContainer = ({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
const { closeCommandMenu } = useCommandMenu();
|
|
||||||
|
|
||||||
const { commandMenuCloseAnimationCompleteCleanup } =
|
const { commandMenuCloseAnimationCompleteCleanup } =
|
||||||
useCommandMenuCloseAnimationCompleteCleanup();
|
useCommandMenuCloseAnimationCompleteCleanup();
|
||||||
|
|
||||||
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
|
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
|
||||||
|
|
||||||
const commandMenuRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useCommandMenuHotKeys();
|
|
||||||
|
|
||||||
const handleClickOutside = useRecoilCallback(
|
|
||||||
({ snapshot }) =>
|
|
||||||
() => {
|
|
||||||
const hotkeyScope = snapshot
|
|
||||||
.getLoadable(currentHotkeyScopeState)
|
|
||||||
.getValue();
|
|
||||||
|
|
||||||
if (hotkeyScope?.scope === CommandMenuHotkeyScope.CommandMenuFocused) {
|
|
||||||
closeCommandMenu();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[closeCommandMenu],
|
|
||||||
);
|
|
||||||
|
|
||||||
useListenClickOutside({
|
|
||||||
refs: [commandMenuRef],
|
|
||||||
callback: handleClickOutside,
|
|
||||||
listenerId: 'COMMAND_MENU_LISTENER_ID',
|
|
||||||
excludeClassNames: ['page-header-command-menu-button'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const isMobile = useIsMobile();
|
|
||||||
|
|
||||||
const targetVariantForAnimation: CommandMenuAnimationVariant = isMobile
|
|
||||||
? 'fullScreen'
|
|
||||||
: 'normal';
|
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const objectMetadataItemId = useRecoilComponentValueV2(
|
const objectMetadataItemId = useRecoilComponentValueV2(
|
||||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||||
COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
||||||
@ -129,18 +67,9 @@ export const CommandMenuContainer = ({
|
|||||||
onExitComplete={commandMenuCloseAnimationCompleteCleanup}
|
onExitComplete={commandMenuCloseAnimationCompleteCleanup}
|
||||||
>
|
>
|
||||||
{isCommandMenuOpened && (
|
{isCommandMenuOpened && (
|
||||||
<StyledCommandMenu
|
<CommandMenuOpenContainer>
|
||||||
data-testid="command-menu"
|
|
||||||
ref={commandMenuRef}
|
|
||||||
className="command-menu"
|
|
||||||
animate={targetVariantForAnimation}
|
|
||||||
initial="closed"
|
|
||||||
exit="closed"
|
|
||||||
variants={COMMAND_MENU_ANIMATION_VARIANTS}
|
|
||||||
transition={{ duration: theme.animation.duration.normal }}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</StyledCommandMenu>
|
</CommandMenuOpenContainer>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</ActionMenuComponentInstanceContext.Provider>
|
</ActionMenuComponentInstanceContext.Provider>
|
||||||
|
|||||||
@ -0,0 +1,85 @@
|
|||||||
|
import { COMMAND_MENU_ANIMATION_VARIANTS } from '@/command-menu/constants/CommandMenuAnimationVariants';
|
||||||
|
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||||
|
import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
|
||||||
|
import { CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant';
|
||||||
|
import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeyScope';
|
||||||
|
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
|
||||||
|
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
||||||
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
import { useIsMobile } from 'twenty-ui/utilities';
|
||||||
|
|
||||||
|
const StyledCommandMenu = styled(motion.div)`
|
||||||
|
background: ${({ theme }) => theme.background.primary};
|
||||||
|
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||||
|
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||||
|
font-family: ${({ theme }) => theme.font.family};
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
position: fixed;
|
||||||
|
right: 0%;
|
||||||
|
top: 0%;
|
||||||
|
z-index: ${RootStackingContextZIndices.CommandMenu};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CommandMenuOpenContainer = ({
|
||||||
|
children,
|
||||||
|
}: React.PropsWithChildren) => {
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
|
const targetVariantForAnimation: CommandMenuAnimationVariant = isMobile
|
||||||
|
? 'fullScreen'
|
||||||
|
: 'normal';
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const { closeCommandMenu } = useCommandMenu();
|
||||||
|
|
||||||
|
const commandMenuRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useCommandMenuHotKeys();
|
||||||
|
|
||||||
|
const handleClickOutside = useRecoilCallback(
|
||||||
|
({ snapshot }) =>
|
||||||
|
() => {
|
||||||
|
const hotkeyScope = snapshot
|
||||||
|
.getLoadable(currentHotkeyScopeState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
if (hotkeyScope?.scope === CommandMenuHotkeyScope.CommandMenuFocused) {
|
||||||
|
closeCommandMenu();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[closeCommandMenu],
|
||||||
|
);
|
||||||
|
|
||||||
|
useListenClickOutside({
|
||||||
|
refs: [commandMenuRef],
|
||||||
|
callback: handleClickOutside,
|
||||||
|
listenerId: 'COMMAND_MENU_LISTENER_ID',
|
||||||
|
excludeClassNames: ['page-header-command-menu-button'],
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledCommandMenu
|
||||||
|
data-testid="command-menu"
|
||||||
|
ref={commandMenuRef}
|
||||||
|
className="command-menu"
|
||||||
|
animate={targetVariantForAnimation}
|
||||||
|
initial="closed"
|
||||||
|
exit="closed"
|
||||||
|
variants={COMMAND_MENU_ANIMATION_VARIANTS}
|
||||||
|
transition={{ duration: theme.animation.duration.normal }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</StyledCommandMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,22 +1,16 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { Key } from 'ts-key-enum';
|
|
||||||
|
|
||||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||||
import { KEYBOARD_SHORTCUTS_GENERAL } from '@/keyboard-shortcut-menu/constants/KeyboardShortcutsGeneral';
|
|
||||||
import { KEYBOARD_SHORTCUTS_TABLE } from '@/keyboard-shortcut-menu/constants/KeyboardShortcutsTable';
|
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||||
|
|
||||||
import { useKeyboardShortcutMenu } from '../hooks/useKeyboardShortcutMenu';
|
import { useKeyboardShortcutMenu } from '../hooks/useKeyboardShortcutMenu';
|
||||||
import { isKeyboardShortcutMenuOpenedState } from '../states/isKeyboardShortcutMenuOpenedState';
|
import { isKeyboardShortcutMenuOpenedState } from '../states/isKeyboardShortcutMenuOpenedState';
|
||||||
|
|
||||||
import { KeyboardMenuDialog } from './KeyboardShortcutMenuDialog';
|
import { KeyboardShortcutMenuOpenContent } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenuOpenContent';
|
||||||
import { KeyboardMenuGroup } from './KeyboardShortcutMenuGroup';
|
|
||||||
import { KeyboardMenuItem } from './KeyboardShortcutMenuItem';
|
|
||||||
|
|
||||||
export const KeyboardShortcutMenu = () => {
|
export const KeyboardShortcutMenu = () => {
|
||||||
const { toggleKeyboardShortcutMenu, closeKeyboardShortcutMenu } =
|
const { toggleKeyboardShortcutMenu } = useKeyboardShortcutMenu();
|
||||||
useKeyboardShortcutMenu();
|
|
||||||
const isKeyboardShortcutMenuOpened = useRecoilValue(
|
const isKeyboardShortcutMenuOpened = useRecoilValue(
|
||||||
isKeyboardShortcutMenuOpenedState,
|
isKeyboardShortcutMenuOpenedState,
|
||||||
);
|
);
|
||||||
@ -32,31 +26,7 @@ export const KeyboardShortcutMenu = () => {
|
|||||||
[toggleKeyboardShortcutMenu],
|
[toggleKeyboardShortcutMenu],
|
||||||
);
|
);
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
[Key.Escape],
|
|
||||||
() => {
|
|
||||||
closeKeyboardShortcutMenu();
|
|
||||||
},
|
|
||||||
AppHotkeyScope.KeyboardShortcutMenuOpen,
|
|
||||||
[closeKeyboardShortcutMenu],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>{isKeyboardShortcutMenuOpened && <KeyboardShortcutMenuOpenContent />}</>
|
||||||
{isKeyboardShortcutMenuOpened && (
|
|
||||||
<KeyboardMenuDialog onClose={toggleKeyboardShortcutMenu}>
|
|
||||||
<KeyboardMenuGroup heading="Table">
|
|
||||||
{KEYBOARD_SHORTCUTS_TABLE.map((TableShortcut, index) => (
|
|
||||||
<KeyboardMenuItem shortcut={TableShortcut} key={index} />
|
|
||||||
))}
|
|
||||||
</KeyboardMenuGroup>
|
|
||||||
<KeyboardMenuGroup heading="General">
|
|
||||||
{KEYBOARD_SHORTCUTS_GENERAL.map((GeneralShortcut) => (
|
|
||||||
<KeyboardMenuItem shortcut={GeneralShortcut} />
|
|
||||||
))}
|
|
||||||
</KeyboardMenuGroup>
|
|
||||||
</KeyboardMenuDialog>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,43 @@
|
|||||||
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
|
import { KEYBOARD_SHORTCUTS_GENERAL } from '@/keyboard-shortcut-menu/constants/KeyboardShortcutsGeneral';
|
||||||
|
import { KEYBOARD_SHORTCUTS_TABLE } from '@/keyboard-shortcut-menu/constants/KeyboardShortcutsTable';
|
||||||
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||||
|
|
||||||
|
import { useKeyboardShortcutMenu } from '../hooks/useKeyboardShortcutMenu';
|
||||||
|
|
||||||
|
import { KeyboardMenuDialog } from './KeyboardShortcutMenuDialog';
|
||||||
|
import { KeyboardMenuGroup } from './KeyboardShortcutMenuGroup';
|
||||||
|
import { KeyboardMenuItem } from './KeyboardShortcutMenuItem';
|
||||||
|
|
||||||
|
export const KeyboardShortcutMenuOpenContent = () => {
|
||||||
|
const { toggleKeyboardShortcutMenu, closeKeyboardShortcutMenu } =
|
||||||
|
useKeyboardShortcutMenu();
|
||||||
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
[Key.Escape],
|
||||||
|
() => {
|
||||||
|
closeKeyboardShortcutMenu();
|
||||||
|
},
|
||||||
|
AppHotkeyScope.KeyboardShortcutMenuOpen,
|
||||||
|
[closeKeyboardShortcutMenu],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<KeyboardMenuDialog onClose={toggleKeyboardShortcutMenu}>
|
||||||
|
<KeyboardMenuGroup heading="Table">
|
||||||
|
{KEYBOARD_SHORTCUTS_TABLE.map((TableShortcut, index) => (
|
||||||
|
<KeyboardMenuItem shortcut={TableShortcut} key={index} />
|
||||||
|
))}
|
||||||
|
</KeyboardMenuGroup>
|
||||||
|
<KeyboardMenuGroup heading="General">
|
||||||
|
{KEYBOARD_SHORTCUTS_GENERAL.map((GeneralShortcut) => (
|
||||||
|
<KeyboardMenuItem shortcut={GeneralShortcut} />
|
||||||
|
))}
|
||||||
|
</KeyboardMenuGroup>
|
||||||
|
</KeyboardMenuDialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -2,7 +2,6 @@ import styled from '@emotion/styled';
|
|||||||
import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350
|
import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350
|
||||||
import { useContext, useRef } from 'react';
|
import { useContext, useRef } from 'react';
|
||||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||||
import { Key } from 'ts-key-enum';
|
|
||||||
|
|
||||||
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
|
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
|
||||||
import { RecordBoardHeader } from '@/object-record/record-board/components/RecordBoardHeader';
|
import { RecordBoardHeader } from '@/object-record/record-board/components/RecordBoardHeader';
|
||||||
@ -136,8 +135,6 @@ export const RecordBoard = () => {
|
|||||||
|
|
||||||
useScopedHotkeys('ctrl+a,meta+a', selectAll, TableHotkeyScope.Table);
|
useScopedHotkeys('ctrl+a,meta+a', selectAll, TableHotkeyScope.Table);
|
||||||
|
|
||||||
useScopedHotkeys(Key.Escape, resetRecordSelection, TableHotkeyScope.Table);
|
|
||||||
|
|
||||||
const setIsRemoveSortingModalOpen = useSetRecoilState(
|
const setIsRemoveSortingModalOpen = useSetRecoilState(
|
||||||
isRemoveSortingModalOpenState,
|
isRemoveSortingModalOpenState,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import { isFieldRelationToOneObject } from '@/object-record/record-field/types/g
|
|||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
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 { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ export const useOpenFieldInputEditMode = () => {
|
|||||||
const { openActivityTargetCellEditMode } =
|
const { openActivityTargetCellEditMode } =
|
||||||
useOpenActivityTargetCellEditMode();
|
useOpenActivityTargetCellEditMode();
|
||||||
|
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
|
||||||
|
|
||||||
const openFieldInput = useRecoilCallback(
|
const openFieldInput = useRecoilCallback(
|
||||||
({ snapshot }) =>
|
({ snapshot }) =>
|
||||||
@ -102,7 +102,7 @@ export const useOpenFieldInputEditMode = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setHotkeyScope(
|
setHotkeyScopeAndMemorizePreviousScope(
|
||||||
DEFAULT_CELL_SCOPE.scope,
|
DEFAULT_CELL_SCOPE.scope,
|
||||||
DEFAULT_CELL_SCOPE.customScopes,
|
DEFAULT_CELL_SCOPE.customScopes,
|
||||||
);
|
);
|
||||||
@ -111,7 +111,7 @@ export const useOpenFieldInputEditMode = () => {
|
|||||||
openActivityTargetCellEditMode,
|
openActivityTargetCellEditMode,
|
||||||
openRelationFromManyFieldInput,
|
openRelationFromManyFieldInput,
|
||||||
openRelationToOneFieldInput,
|
openRelationToOneFieldInput,
|
||||||
setHotkeyScope,
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
FieldInputClickOutsideEvent,
|
FieldInputClickOutsideEvent,
|
||||||
FieldInputEvent,
|
FieldInputEvent,
|
||||||
} from '@/object-record/record-field/types/FieldInputEvent';
|
} from '@/object-record/record-field/types/FieldInputEvent';
|
||||||
|
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
|
||||||
|
|
||||||
type FullNameFieldInputProps = {
|
type FullNameFieldInputProps = {
|
||||||
onClickOutside?: FieldInputClickOutsideEvent;
|
onClickOutside?: FieldInputClickOutsideEvent;
|
||||||
@ -25,7 +26,7 @@ export const FullNameFieldInput = ({
|
|||||||
onTab,
|
onTab,
|
||||||
onShiftTab,
|
onShiftTab,
|
||||||
}: FullNameFieldInputProps) => {
|
}: FullNameFieldInputProps) => {
|
||||||
const { draftValue, setDraftValue, persistFullNameField, fieldDefinition } =
|
const { draftValue, setDraftValue, persistFullNameField } =
|
||||||
useFullNameField();
|
useFullNameField();
|
||||||
|
|
||||||
const convertToFullName = (newDoubleText: FieldDoubleText) => {
|
const convertToFullName = (newDoubleText: FieldDoubleText) => {
|
||||||
@ -93,7 +94,7 @@ export const FullNameFieldInput = ({
|
|||||||
onShiftTab={handleShiftTab}
|
onShiftTab={handleShiftTab}
|
||||||
onTab={handleTab}
|
onTab={handleTab}
|
||||||
onPaste={handlePaste}
|
onPaste={handlePaste}
|
||||||
hotkeyScope={`full-name-field-input-${fieldDefinition.metadata.fieldName}`}
|
hotkeyScope={DEFAULT_CELL_SCOPE.scope}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -50,7 +50,7 @@ export const SelectFieldInput = ({
|
|||||||
onCancel?.();
|
onCancel?.();
|
||||||
resetSelectedItem();
|
resetSelectedItem();
|
||||||
},
|
},
|
||||||
`select-field-input-${fieldDefinition.metadata.fieldName}`,
|
DEFAULT_CELL_SCOPE.scope,
|
||||||
[onCancel, resetSelectedItem],
|
[onCancel, resetSelectedItem],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFie
|
|||||||
import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode';
|
import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode';
|
||||||
|
|
||||||
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
||||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
|
||||||
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
import { useIcons } from 'twenty-ui/display';
|
import { useIcons } from 'twenty-ui/display';
|
||||||
@ -80,9 +80,11 @@ export const RecordInlineCell = ({ loading }: RecordInlineCellProps) => {
|
|||||||
const hotkeyScope = snapshot
|
const hotkeyScope = snapshot
|
||||||
.getLoadable(currentHotkeyScopeState)
|
.getLoadable(currentHotkeyScopeState)
|
||||||
.getValue();
|
.getValue();
|
||||||
if (hotkeyScope.scope !== InlineCellHotkeyScope.InlineCell) {
|
|
||||||
|
if (hotkeyScope.scope !== DEFAULT_CELL_SCOPE.scope) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
|
|
||||||
persistField();
|
persistField();
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import { RecordTableBodyUnselectEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyUnselectEffect';
|
import { RecordTableBodyEscapeHotkeyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyEscapeHotkeyEffect';
|
||||||
|
import { RecordTableBodySoftFocusClickOutsideEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodySoftFocusClickOutsideEffect';
|
||||||
|
import { RecordTableBodySoftFocusKeyboardEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodySoftFocusKeyboardEffect';
|
||||||
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 { isAtLeastOneTableRowSelectedSelector } from '@/object-record/record-table/record-table-row/states/isAtLeastOneTableRowSelectedSelector';
|
||||||
|
import { isSoftFocusActiveComponentState } from '@/object-record/record-table/states/isSoftFocusActiveComponentState';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export interface RecordTableBodyEffectsWrapperProps {
|
export interface RecordTableBodyEffectsWrapperProps {
|
||||||
hasRecordGroups: boolean;
|
hasRecordGroups: boolean;
|
||||||
@ -10,13 +15,29 @@ export interface RecordTableBodyEffectsWrapperProps {
|
|||||||
export const RecordTableBodyEffectsWrapper = ({
|
export const RecordTableBodyEffectsWrapper = ({
|
||||||
hasRecordGroups,
|
hasRecordGroups,
|
||||||
tableBodyRef,
|
tableBodyRef,
|
||||||
}: RecordTableBodyEffectsWrapperProps) => (
|
}: RecordTableBodyEffectsWrapperProps) => {
|
||||||
<>
|
const isAtLeastOneRecordSelected = useRecoilComponentValueV2(
|
||||||
{hasRecordGroups ? (
|
isAtLeastOneTableRowSelectedSelector,
|
||||||
<RecordTableRecordGroupBodyEffects />
|
);
|
||||||
) : (
|
|
||||||
<RecordTableNoRecordGroupBodyEffect />
|
const isSoftFocusActiveState = useRecoilComponentValueV2(
|
||||||
)}
|
isSoftFocusActiveComponentState,
|
||||||
<RecordTableBodyUnselectEffect tableBodyRef={tableBodyRef} />
|
);
|
||||||
</>
|
|
||||||
);
|
return (
|
||||||
|
<>
|
||||||
|
{hasRecordGroups ? (
|
||||||
|
<RecordTableRecordGroupBodyEffects />
|
||||||
|
) : (
|
||||||
|
<RecordTableNoRecordGroupBodyEffect />
|
||||||
|
)}
|
||||||
|
{isAtLeastOneRecordSelected && <RecordTableBodyEscapeHotkeyEffect />}
|
||||||
|
{isSoftFocusActiveState && <RecordTableBodySoftFocusKeyboardEffect />}
|
||||||
|
{isSoftFocusActiveState && (
|
||||||
|
<RecordTableBodySoftFocusClickOutsideEffect
|
||||||
|
tableBodyRef={tableBodyRef}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@ -10,14 +10,12 @@ import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
|||||||
import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
|
import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
|
||||||
import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField';
|
import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField';
|
||||||
|
|
||||||
import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext';
|
|
||||||
import { useRecordTable } from '../hooks/useRecordTable';
|
|
||||||
|
|
||||||
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 { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { Key } from 'ts-key-enum';
|
import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext';
|
||||||
|
import { useRecordTable } from '../hooks/useRecordTable';
|
||||||
|
|
||||||
const StyledTableContainer = styled.div`
|
const StyledTableContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -39,10 +37,9 @@ export const RecordTableWithWrappers = ({
|
|||||||
recordTableId,
|
recordTableId,
|
||||||
viewBarId,
|
viewBarId,
|
||||||
}: RecordTableWithWrappersProps) => {
|
}: RecordTableWithWrappersProps) => {
|
||||||
const { resetTableRowSelection, selectAllRows, setHasUserSelectedAllRows } =
|
const { selectAllRows, setHasUserSelectedAllRows } = useRecordTable({
|
||||||
useRecordTable({
|
recordTableId,
|
||||||
recordTableId,
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const handleSelectAllRows = () => {
|
const handleSelectAllRows = () => {
|
||||||
setHasUserSelectedAllRows(true);
|
setHasUserSelectedAllRows(true);
|
||||||
@ -55,8 +52,6 @@ export const RecordTableWithWrappers = ({
|
|||||||
TableHotkeyScope.Table,
|
TableHotkeyScope.Table,
|
||||||
);
|
);
|
||||||
|
|
||||||
useScopedHotkeys(Key.Escape, resetTableRowSelection, TableHotkeyScope.Table);
|
|
||||||
|
|
||||||
const { saveViewFields } = useSaveCurrentViewFields();
|
const { saveViewFields } = useSaveCurrentViewFields();
|
||||||
|
|
||||||
const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular });
|
const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular });
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
|
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
|
||||||
|
export const RecordTableBodyEscapeHotkeyEffect = () => {
|
||||||
|
const { recordTableId } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
|
const { resetTableRowSelection } = useRecordTable({
|
||||||
|
recordTableId,
|
||||||
|
});
|
||||||
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
[Key.Escape],
|
||||||
|
() => {
|
||||||
|
resetTableRowSelection();
|
||||||
|
},
|
||||||
|
TableHotkeyScope.Table,
|
||||||
|
);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
@ -1,38 +1,19 @@
|
|||||||
import { Key } from 'ts-key-enum';
|
|
||||||
|
|
||||||
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 { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
|
||||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
|
|
||||||
type RecordTableBodyUnselectEffectProps = {
|
type RecordTableBodySoftFocusClickOutsideEffectProps = {
|
||||||
tableBodyRef: React.RefObject<HTMLDivElement>;
|
tableBodyRef: React.RefObject<HTMLDivElement>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordTableBodyUnselectEffect = ({
|
export const RecordTableBodySoftFocusClickOutsideEffect = ({
|
||||||
tableBodyRef,
|
tableBodyRef,
|
||||||
}: RecordTableBodyUnselectEffectProps) => {
|
}: RecordTableBodySoftFocusClickOutsideEffectProps) => {
|
||||||
const { recordTableId } = useRecordTableContextOrThrow();
|
const { recordTableId } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
const leaveTableFocus = useLeaveTableFocus(recordTableId);
|
const leaveTableFocus = useLeaveTableFocus(recordTableId);
|
||||||
|
|
||||||
const { resetTableRowSelection, useMapKeyboardToSoftFocus } = useRecordTable({
|
|
||||||
recordTableId,
|
|
||||||
});
|
|
||||||
|
|
||||||
useMapKeyboardToSoftFocus();
|
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
[Key.Escape],
|
|
||||||
() => {
|
|
||||||
resetTableRowSelection();
|
|
||||||
},
|
|
||||||
TableHotkeyScope.Table,
|
|
||||||
);
|
|
||||||
|
|
||||||
useListenClickOutside({
|
useListenClickOutside({
|
||||||
excludeClassNames: [
|
excludeClassNames: [
|
||||||
'bottom-bar',
|
'bottom-bar',
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
|
|
||||||
|
export const RecordTableBodySoftFocusKeyboardEffect = () => {
|
||||||
|
const { recordTableId } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
|
const { useMapKeyboardToSoftFocus } = useRecordTable({
|
||||||
|
recordTableId,
|
||||||
|
});
|
||||||
|
|
||||||
|
useMapKeyboardToSoftFocus();
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
@ -1,26 +1,55 @@
|
|||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
||||||
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
||||||
|
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||||
|
import { RecordTableCellEditButton } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditButton';
|
||||||
|
import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell';
|
||||||
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer';
|
import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer';
|
||||||
|
|
||||||
export const RecordTableCellDisplayMode = ({
|
export const RecordTableCellDisplayMode = ({
|
||||||
children,
|
children,
|
||||||
softFocus,
|
}: React.PropsWithChildren) => {
|
||||||
}: React.PropsWithChildren<{ softFocus?: boolean }>) => {
|
const { recordId, isReadOnly } = useContext(FieldContext);
|
||||||
const { recordId } = useContext(FieldContext);
|
|
||||||
|
const { columnIndex, hasSoftFocus } = useContext(RecordTableCellContext);
|
||||||
|
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const { onActionMenuDropdownOpened } = useRecordTableBodyContextOrThrow();
|
const { onActionMenuDropdownOpened } = useRecordTableBodyContextOrThrow();
|
||||||
|
|
||||||
|
const { openTableCell } = useOpenRecordTableCellFromCell();
|
||||||
|
|
||||||
|
const isFieldInputOnly = useIsFieldInputOnly();
|
||||||
|
const isFirstColumn = columnIndex === 0;
|
||||||
|
|
||||||
|
const showButton =
|
||||||
|
hasSoftFocus &&
|
||||||
|
!isFieldInputOnly &&
|
||||||
|
!isReadOnly &&
|
||||||
|
!(isMobile && isFirstColumn);
|
||||||
|
|
||||||
const handleActionMenuDropdown = (event: React.MouseEvent) => {
|
const handleActionMenuDropdown = (event: React.MouseEvent) => {
|
||||||
onActionMenuDropdownOpened(event, recordId);
|
onActionMenuDropdownOpened(event, recordId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (!isFieldInputOnly && !isReadOnly) {
|
||||||
|
openTableCell();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordTableCellDisplayContainer
|
<>
|
||||||
softFocus={softFocus}
|
<RecordTableCellDisplayContainer
|
||||||
onContextMenu={handleActionMenuDropdown}
|
softFocus={hasSoftFocus}
|
||||||
>
|
onContextMenu={handleActionMenuDropdown}
|
||||||
{children}
|
onClick={handleClick}
|
||||||
</RecordTableCellDisplayContainer>
|
>
|
||||||
|
{children}
|
||||||
|
</RecordTableCellDisplayContainer>
|
||||||
|
{showButton && <RecordTableCellEditButton />}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
import { useGetButtonIcon } from '@/object-record/record-field/hooks/useGetButtonIcon';
|
||||||
|
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
||||||
|
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||||
|
import { RecordTableCellButton } from '@/object-record/record-table/record-table-cell/components/RecordTableCellButton';
|
||||||
|
import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { IconArrowUpRight, IconPencil } from 'twenty-ui/display';
|
||||||
|
|
||||||
|
export const RecordTableCellEditButton = () => {
|
||||||
|
const { columnIndex } = useContext(RecordTableCellContext);
|
||||||
|
|
||||||
|
const { openTableCell } = useOpenRecordTableCellFromCell();
|
||||||
|
|
||||||
|
const isFieldInputOnly = useIsFieldInputOnly();
|
||||||
|
const isFirstColumn = columnIndex === 0;
|
||||||
|
const customButtonIcon = useGetButtonIcon();
|
||||||
|
|
||||||
|
const buttonIcon = isFirstColumn
|
||||||
|
? IconArrowUpRight
|
||||||
|
: isDefined(customButtonIcon)
|
||||||
|
? customButtonIcon
|
||||||
|
: IconPencil;
|
||||||
|
|
||||||
|
const handleButtonClick = () => {
|
||||||
|
if (!isFieldInputOnly && isFirstColumn) {
|
||||||
|
openTableCell(undefined, false, true);
|
||||||
|
} else {
|
||||||
|
openTableCell();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RecordTableCellButton onClick={handleButtonClick} Icon={buttonIcon} />
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||||
|
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||||
|
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||||
|
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
|
||||||
|
|
||||||
|
export const isAtLeastOneTableRowSelectedSelector =
|
||||||
|
createComponentSelectorV2<boolean>({
|
||||||
|
key: 'isAtLeastOneTableRowSelectedSelector',
|
||||||
|
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||||
|
get:
|
||||||
|
({ instanceId }) =>
|
||||||
|
({ get }) => {
|
||||||
|
const allRecordIds = get(
|
||||||
|
recordIndexAllRecordIdsComponentSelector.selectorFamily({
|
||||||
|
instanceId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const isAnyRecordSelected = allRecordIds.some((recordId) =>
|
||||||
|
get(
|
||||||
|
isRowSelectedComponentFamilyState.atomFamily({
|
||||||
|
instanceId,
|
||||||
|
familyKey: recordId,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return isAnyRecordSelected;
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -4,6 +4,7 @@ import { FieldContext } from '@/object-record/record-field/contexts/FieldContext
|
|||||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||||
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
|
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
|
||||||
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
|
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
|
||||||
|
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
|
||||||
import { RecordTitleCellTextFieldInput } from '@/object-record/record-title-cell/components/RecordTitleCellTextFieldInput';
|
import { RecordTitleCellTextFieldInput } from '@/object-record/record-title-cell/components/RecordTitleCellTextFieldInput';
|
||||||
import { RecordTitleFullNameFieldInput } from '@/object-record/record-title-cell/components/RecordTitleFullNameFieldInput';
|
import { RecordTitleFullNameFieldInput } from '@/object-record/record-title-cell/components/RecordTitleFullNameFieldInput';
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ export const RecordTitleCellFieldInput = ({
|
|||||||
onTab,
|
onTab,
|
||||||
onClickOutside,
|
onClickOutside,
|
||||||
}: RecordTitleCellFieldInputProps) => {
|
}: RecordTitleCellFieldInputProps) => {
|
||||||
const { fieldDefinition, recordId } = useContext(FieldContext);
|
const { fieldDefinition } = useContext(FieldContext);
|
||||||
|
|
||||||
if (!isFieldText(fieldDefinition) && !isFieldFullName(fieldDefinition)) {
|
if (!isFieldText(fieldDefinition) && !isFieldFullName(fieldDefinition)) {
|
||||||
throw new Error('Field definition is not a text or full name field');
|
throw new Error('Field definition is not a text or full name field');
|
||||||
@ -43,7 +44,7 @@ export const RecordTitleCellFieldInput = ({
|
|||||||
onTab={onTab}
|
onTab={onTab}
|
||||||
onShiftTab={onShiftTab}
|
onShiftTab={onShiftTab}
|
||||||
sizeVariant={sizeVariant}
|
sizeVariant={sizeVariant}
|
||||||
hotkeyScope={`record-title-cell-text-field-input-${recordId}`}
|
hotkeyScope={DEFAULT_CELL_SCOPE.scope}
|
||||||
/>
|
/>
|
||||||
) : isFieldFullName(fieldDefinition) ? (
|
) : isFieldFullName(fieldDefinition) ? (
|
||||||
<RecordTitleFullNameFieldInput
|
<RecordTitleFullNameFieldInput
|
||||||
@ -53,7 +54,7 @@ export const RecordTitleCellFieldInput = ({
|
|||||||
onTab={onTab}
|
onTab={onTab}
|
||||||
onShiftTab={onShiftTab}
|
onShiftTab={onShiftTab}
|
||||||
sizeVariant={sizeVariant}
|
sizeVariant={sizeVariant}
|
||||||
hotkeyScope={`record-title-cell-full-name-field-input-${recordId}`}
|
hotkeyScope={DEFAULT_CELL_SCOPE.scope}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -140,7 +140,7 @@ export const Dropdown = ({
|
|||||||
dropdownHotkeyScope,
|
dropdownHotkeyScope,
|
||||||
);
|
);
|
||||||
|
|
||||||
toggleDropdown();
|
toggleDropdown(dropdownHotkeyScope);
|
||||||
onClickOutside?.();
|
onClickOutside?.();
|
||||||
},
|
},
|
||||||
[dropdownId, dropdownHotkeyScope, onClickOutside, toggleDropdown],
|
[dropdownId, dropdownHotkeyScope, onClickOutside, toggleDropdown],
|
||||||
|
|||||||
@ -83,11 +83,11 @@ export const useDropdown = (dropdownId?: string) => {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggleDropdown = () => {
|
const toggleDropdown = (dropdownHotkeyScopeFromProps?: HotkeyScope) => {
|
||||||
if (isDropdownOpen) {
|
if (isDropdownOpen) {
|
||||||
closeDropdown();
|
closeDropdown();
|
||||||
} else {
|
} else {
|
||||||
openDropdown();
|
openDropdown(dropdownHotkeyScopeFromProps);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -20,11 +20,18 @@ export const usePreviousHotkeyScope = (memoizeKey = 'global') => {
|
|||||||
.getValue();
|
.getValue();
|
||||||
|
|
||||||
if (!previousHotkeyScope) {
|
if (!previousHotkeyScope) {
|
||||||
|
if (DEBUG_HOTKEY_SCOPE) {
|
||||||
|
logDebug(`DEBUG: no previous hotkey scope ${memoizeKey}`);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DEBUG_HOTKEY_SCOPE) {
|
if (DEBUG_HOTKEY_SCOPE) {
|
||||||
logDebug('DEBUG: goBackToPreviousHotkeyScope', previousHotkeyScope);
|
logDebug(
|
||||||
|
`DEBUG: goBackToPreviousHotkeyScope ${previousHotkeyScope.scope}`,
|
||||||
|
previousHotkeyScope,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setHotkeyScope(
|
setHotkeyScope(
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { DEBUG_HOTKEY_SCOPE } from '@/ui/utilities/hotkey/hooks/useScopedHotkeyCallback';
|
import { DEBUG_HOTKEY_SCOPE } from '@/ui/utilities/hotkey/hooks/useScopedHotkeyCallback';
|
||||||
import { logDebug } from '~/utils/logDebug';
|
|
||||||
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { logDebug } from '~/utils/logDebug';
|
||||||
import { DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES } from '../constants/DefaultHotkeysScopeCustomScopes';
|
import { DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES } from '../constants/DefaultHotkeysScopeCustomScopes';
|
||||||
import { currentHotkeyScopeState } from '../states/internal/currentHotkeyScopeState';
|
import { currentHotkeyScopeState } from '../states/internal/currentHotkeyScopeState';
|
||||||
import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState';
|
import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState';
|
||||||
@ -84,7 +84,7 @@ export const useSetHotkeyScope = () =>
|
|||||||
scopesToSet.push(newHotkeyScope.scope);
|
scopesToSet.push(newHotkeyScope.scope);
|
||||||
|
|
||||||
if (DEBUG_HOTKEY_SCOPE) {
|
if (DEBUG_HOTKEY_SCOPE) {
|
||||||
logDebug('DEBUG: set new hotkey scope', {
|
logDebug(`DEBUG: set new hotkey scope : ${newHotkeyScope.scope}`, {
|
||||||
scopesToSet,
|
scopesToSet,
|
||||||
newHotkeyScope,
|
newHotkeyScope,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -162,6 +162,21 @@ export const useListenClickOutside = <T extends Element>({
|
|||||||
!isMouseDownInside &&
|
!isMouseDownInside &&
|
||||||
!isClickedOnExcluded;
|
!isClickedOnExcluded;
|
||||||
|
|
||||||
|
if (CLICK_OUTSIDE_DEBUG_MODE) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('click outside compare ref', {
|
||||||
|
listenerId,
|
||||||
|
shouldTrigger,
|
||||||
|
clickedOnAtLeastOneRef,
|
||||||
|
isMouseDownInside,
|
||||||
|
isListening,
|
||||||
|
hasMouseDownHappened,
|
||||||
|
isClickedOnExcluded,
|
||||||
|
enabled,
|
||||||
|
event,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldTrigger) {
|
if (shouldTrigger) {
|
||||||
callback(event);
|
callback(event);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user