Replace hotkey scopes by focus stack (Part 1 - Dropdowns and Side Panel) (#12673)

This PR is the first part of a refactoring aiming to deprecate the
hotkey scopes api in favor of the new focus stack api which is more
robust.

The refactored components in this PR are the dropdowns and the side
panel/command menu.

- Replaced `useScopedHotkeys` by `useHotkeysOnFocusedElement` for all
dropdown components, selectable lists and the command menu
- Introduced `focusId` for all dropdowns and created a common hotkey
scope `DropdownHotkeyScope` for backward compatibility
- Replaced `setHotkeyScopeAndMemorizePreviousScope` occurrences with
`usePushFocusItemToFocusStack` and `goBackToPreviousHotkeyScope` with
`removeFocusItemFromFocusStack`

Note: Test that the shorcuts and arrow key navigation still work
properly when interacting with dropdowns and the command menu.

Bugs that I have spotted during the QA but which are already present on
main:
- Icon picker select with arrow keys doesn’t work inside dropdowns
- Some dropdowns are not selectable with arrow keys (no selectable list)
- Dropdowns in dropdowns don’t reset the hotkey scope correctly when
closing
- The table click outside is not triggered after closing a table cell
and clicking outside of the table
This commit is contained in:
Raphaël Bosi
2025-06-19 14:53:18 +02:00
committed by GitHub
parent 6dd3a71497
commit cbc0d06a2f
155 changed files with 977 additions and 845 deletions

View File

@ -1,4 +1,5 @@
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { Button } from 'twenty-ui/input'; import { Button } from 'twenty-ui/input';
@ -13,12 +14,13 @@ export const CmdEnterActionButton = ({
onClick: () => void; onClick: () => void;
disabled?: boolean; disabled?: boolean;
}) => { }) => {
useScopedHotkeys( useHotkeysOnFocusedElement({
[`${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`], keys: [`${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`],
() => onClick(), callback: () => onClick(),
AppHotkeyScope.CommandMenuOpen, focusId: SIDE_PANEL_FOCUS_ID,
[onClick], scope: AppHotkeyScope.CommandMenuOpen,
); dependencies: [onClick],
});
return ( return (
<Button <Button

View File

@ -2,15 +2,16 @@ import { ActionComponent } from '@/action-menu/actions/display/components/Action
import { ActionScope } from '@/action-menu/actions/types/ActionScope'; import { ActionScope } from '@/action-menu/actions/types/ActionScope';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { CommandMenuActionMenuDropdownHotkeyScope } from '@/action-menu/types/CommandMenuActionMenuDropdownHotkeyScope';
import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getRightDrawerActionMenuDropdownIdFromActionMenuId'; import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getRightDrawerActionMenuDropdownIdFromActionMenuId';
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
@ -25,24 +26,30 @@ export const CommandMenuActionMenuDropdown = () => {
ActionMenuComponentInstanceContext, ActionMenuComponentInstanceContext,
); );
const { toggleDropdown } = useDropdownV2();
const theme = useTheme(); const theme = useTheme();
useScopedHotkeys( const dropdownId =
['ctrl+o,meta+o'], getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId);
() => { const { toggleDropdown } = useDropdownV2();
toggleDropdown(
getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId), const hotkeysConfig = {
{ keys: ['ctrl+o', 'meta+o'],
scope: callback: () => {
CommandMenuActionMenuDropdownHotkeyScope.CommandMenuActionMenuDropdown, toggleDropdown(dropdownId);
},
);
}, },
AppHotkeyScope.CommandMenuOpen, scope: AppHotkeyScope.CommandMenuOpen,
[toggleDropdown], dependencies: [toggleDropdown],
); };
useHotkeysOnFocusedElement({
...hotkeysConfig,
focusId: SIDE_PANEL_FOCUS_ID,
});
useHotkeysOnFocusedElement({
...hotkeysConfig,
focusId: dropdownId,
});
const recordSelectionActions = actions.filter( const recordSelectionActions = actions.filter(
(action) => action.scope === ActionScope.RecordSelection, (action) => action.scope === ActionScope.RecordSelection,
@ -56,19 +63,17 @@ export const CommandMenuActionMenuDropdown = () => {
return ( return (
<Dropdown <Dropdown
dropdownId={getRightDrawerActionMenuDropdownIdFromActionMenuId( dropdownId={dropdownId}
actionMenuId,
)}
dropdownHotkeyScope={{
scope:
CommandMenuActionMenuDropdownHotkeyScope.CommandMenuActionMenuDropdown,
}}
data-select-disable data-select-disable
clickableComponent={ clickableComponent={
<Button title="Options" hotkeys={[getOsControlSymbol(), 'O']} /> <Button title="Options" hotkeys={[getOsControlSymbol(), 'O']} />
} }
dropdownPlacement="top-end" dropdownPlacement="top-end"
dropdownOffset={{ y: parseInt(theme.spacing(2), 10) }} dropdownOffset={{ y: parseInt(theme.spacing(2), 10) }}
globalHotkeysConfig={{
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: false,
}}
onOpen={() => { onOpen={() => {
setSelectedItemId(selectableItemIdArray[0]); setSelectedItemId(selectableItemIdArray[0]);
}} }}
@ -77,10 +82,9 @@ export const CommandMenuActionMenuDropdown = () => {
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<SelectableList <SelectableList
selectableListInstanceId={actionMenuId} selectableListInstanceId={actionMenuId}
hotkeyScope={ focusId={dropdownId}
CommandMenuActionMenuDropdownHotkeyScope.CommandMenuActionMenuDropdown
}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{recordSelectionActions.map((action) => ( {recordSelectionActions.map((action) => (
<ActionComponent action={action} key={action.key} /> <ActionComponent action={action} key={action.key} />

View File

@ -5,12 +5,12 @@ import { ACTION_MENU_DROPDOWN_CLICK_OUTSIDE_ID } from '@/action-menu/constants/A
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState'; import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId'; import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@ -72,9 +72,6 @@ export const RecordIndexActionMenuDropdown = () => {
return ( return (
<Dropdown <Dropdown
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownHotkeyScope={{
scope: ActionMenuDropdownHotkeyScope.ActionMenuDropdown,
}}
data-select-disable data-select-disable
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownStrategy="absolute" dropdownStrategy="absolute"
@ -89,9 +86,10 @@ export const RecordIndexActionMenuDropdown = () => {
> >
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<SelectableList <SelectableList
hotkeyScope={ActionMenuDropdownHotkeyScope.ActionMenuDropdown} focusId={dropdownId}
selectableItemIdArray={selectedItemIdArray} selectableItemIdArray={selectedItemIdArray}
selectableListInstanceId={dropdownId} selectableListInstanceId={dropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{recordIndexActions.map((action) => ( {recordIndexActions.map((action) => (
<ActionComponent action={action} key={action.key} /> <ActionComponent action={action} key={action.key} />

View File

@ -1,5 +1,6 @@
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getRightDrawerActionMenuDropdownIdFromActionMenuId'; import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getRightDrawerActionMenuDropdownIdFromActionMenuId';
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext'; import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
@ -11,7 +12,7 @@ import { AppPath } from '@/types/AppPath';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId'; import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext'; import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext';
@ -118,12 +119,13 @@ export const RecordShowRightDrawerOpenRecordButton = ({
], ],
); );
useScopedHotkeys( useHotkeysOnFocusedElement({
['ctrl+Enter,meta+Enter'], keys: ['ctrl+Enter,meta+Enter'],
handleOpenRecord, callback: handleOpenRecord,
AppHotkeyScope.CommandMenuOpen, focusId: SIDE_PANEL_FOCUS_ID,
[handleOpenRecord], scope: AppHotkeyScope.CommandMenuOpen,
); dependencies: [handleOpenRecord],
});
if (!isDefined(record)) { if (!isDefined(record)) {
return null; return null;

View File

@ -1,3 +0,0 @@
export enum ActionMenuDropdownHotkeyScope {
ActionMenuDropdown = 'action-menu-dropdown',
}

View File

@ -1,3 +0,0 @@
export enum CommandMenuActionMenuDropdownHotkeyScope {
CommandMenuActionMenuDropdown = 'command-menu-action-menu-dropdown',
}

View File

@ -72,7 +72,6 @@ export const AttachmentDropdown = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
/> />
); );
}; };

View File

@ -70,6 +70,7 @@ export const ActivityTargetsInlineCell = ({
labelWidth: fieldDefinition?.labelWidth, labelWidth: fieldDefinition?.labelWidth,
editModeContent: ( editModeContent: (
<MultipleRecordPicker <MultipleRecordPicker
focusId={componentInstanceId}
componentInstanceId={componentInstanceId} componentInstanceId={componentInstanceId}
onClickOutside={() => { onClickOutside={() => {
closeInlineCell(); closeInlineCell();

View File

@ -5,8 +5,9 @@ import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-pic
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState'; import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState'; import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState';
import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState'; import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState';
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope'; import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
type OpenActivityTargetCellEditModeProps = { type OpenActivityTargetCellEditModeProps = {
@ -19,7 +20,7 @@ export const useOpenActivityTargetCellEditMode = () => {
const { performSearch: multipleRecordPickerPerformSearch } = const { performSearch: multipleRecordPickerPerformSearch } =
useMultipleRecordPickerPerformSearch(); useMultipleRecordPickerPerformSearch();
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openActivityTargetCellEditMode = useRecoilCallback( const openActivityTargetCellEditMode = useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
@ -84,11 +85,19 @@ export const useOpenActivityTargetCellEditMode = () => {
), ),
}); });
setHotkeyScopeAndMemorizePreviousScope({ pushFocusItemToFocusStack({
scope: MultipleRecordPickerHotkeyScope.MultipleRecordPicker, focusId: recordPickerInstanceId,
component: {
type: FocusComponentType.DROPDOWN,
instanceId: recordPickerInstanceId,
},
hotkeyScope: {
scope: DropdownHotkeyScope.Dropdown,
},
memoizeKey: recordPickerInstanceId,
}); });
}, },
[multipleRecordPickerPerformSearch, setHotkeyScopeAndMemorizePreviousScope], [multipleRecordPickerPerformSearch, pushFocusItemToFocusStack],
); );
return { openActivityTargetCellEditMode }; return { openActivityTargetCellEditMode };

View File

@ -3,7 +3,6 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { MenuItem } from 'twenty-ui/navigation'; import { MenuItem } from 'twenty-ui/navigation';
import { import {
@ -72,9 +71,6 @@ export const CommandMenuContextChipGroups = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{
scope: AppHotkeyScope.CommandMenu,
}}
dropdownId={COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID} dropdownId={COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
></Dropdown> ></Dropdown>

View File

@ -4,6 +4,7 @@ import { ActionGroupConfig } from '@/command-menu/components/CommandMenu';
import { CommandMenuDefaultSelectionEffect } from '@/command-menu/components/CommandMenuDefaultSelectionEffect'; import { CommandMenuDefaultSelectionEffect } from '@/command-menu/components/CommandMenuDefaultSelectionEffect';
import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight'; import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight';
import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding'; import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding';
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState'; import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
@ -75,8 +76,9 @@ export const CommandMenuList = ({
<StyledInnerList> <StyledInnerList>
<SelectableList <SelectableList
selectableListInstanceId="command-menu-list" selectableListInstanceId="command-menu-list"
hotkeyScope={AppHotkeyScope.CommandMenuOpen} focusId={SIDE_PANEL_FOCUS_ID}
selectableItemIdArray={selectableItemIds} selectableItemIdArray={selectableItemIds}
hotkeyScope={AppHotkeyScope.CommandMenuOpen}
onSelect={() => { onSelect={() => {
setHasUserSelectedCommand(true); setHasUserSelectedCommand(true);
}} }}

View File

@ -0,0 +1 @@
export const SIDE_PANEL_FOCUS_ID = 'command-menu';

View File

@ -3,12 +3,13 @@ import { useRecoilCallback } from 'recoil';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId'; import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu'; import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState'; import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown'; import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown';
import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState'; import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { useRemoveFocusItemFromFocusStack } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStack';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { IconDotsVertical } from 'twenty-ui/display'; import { IconDotsVertical } from 'twenty-ui/display';
import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState'; import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
@ -16,7 +17,8 @@ import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
export const useCommandMenu = () => { export const useCommandMenu = () => {
const { navigateCommandMenu } = useNavigateCommandMenu(); const { navigateCommandMenu } = useNavigateCommandMenu();
const { closeAnyOpenDropdown } = useCloseAnyOpenDropdown(); const { closeAnyOpenDropdown } = useCloseAnyOpenDropdown();
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
const { removeFocusItemFromFocusStack } = useRemoveFocusItemFromFocusStack();
const closeCommandMenu = useRecoilCallback( const closeCommandMenu = useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
@ -30,10 +32,13 @@ export const useCommandMenu = () => {
set(isCommandMenuClosingState, true); set(isCommandMenuClosingState, true);
set(isDragSelectionStartEnabledState, true); set(isDragSelectionStartEnabledState, true);
closeAnyOpenDropdown(); closeAnyOpenDropdown();
goBackToPreviousHotkeyScope(COMMAND_MENU_COMPONENT_INSTANCE_ID); removeFocusItemFromFocusStack({
focusId: SIDE_PANEL_FOCUS_ID,
memoizeKey: COMMAND_MENU_COMPONENT_INSTANCE_ID,
});
} }
}, },
[closeAnyOpenDropdown, goBackToPreviousHotkeyScope], [closeAnyOpenDropdown, removeFocusItemFromFocusStack],
); );
const openCommandMenu = useCallback(() => { const openCommandMenu = useCallback(() => {

View File

@ -1,4 +1,5 @@
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId'; import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup'; import { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup';
import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates'; import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState'; import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
@ -13,7 +14,8 @@ import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeySc
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState'; import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { IconComponent } from 'twenty-ui/display'; import { IconComponent } from 'twenty-ui/display';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
@ -27,13 +29,13 @@ export type CommandMenuNavigationStackItem = {
}; };
export const useNavigateCommandMenu = () => { export const useNavigateCommandMenu = () => {
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
const { copyContextStoreStates } = useCopyContextStoreStates(); const { copyContextStoreStates } = useCopyContextStoreStates();
const { commandMenuCloseAnimationCompleteCleanup } = const { commandMenuCloseAnimationCompleteCleanup } =
useCommandMenuCloseAnimationCompleteCleanup(); useCommandMenuCloseAnimationCompleteCleanup();
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openCommandMenu = useRecoilCallback( const openCommandMenu = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
() => { () => {
@ -53,10 +55,17 @@ export const useNavigateCommandMenu = () => {
return; return;
} }
setHotkeyScopeAndMemorizePreviousScope({ pushFocusItemToFocusStack({
scope: CommandMenuHotkeyScope.CommandMenuFocused, focusId: SIDE_PANEL_FOCUS_ID,
customScopes: { component: {
commandMenuOpen: true, type: FocusComponentType.SIDE_PANEL,
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
},
hotkeyScope: {
scope: CommandMenuHotkeyScope.CommandMenuFocused,
customScopes: {
commandMenuOpen: true,
},
}, },
memoizeKey: COMMAND_MENU_COMPONENT_INSTANCE_ID, memoizeKey: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}); });
@ -73,7 +82,7 @@ export const useNavigateCommandMenu = () => {
[ [
copyContextStoreStates, copyContextStoreStates,
commandMenuCloseAnimationCompleteCleanup, commandMenuCloseAnimationCompleteCleanup,
setHotkeyScopeAndMemorizePreviousScope, pushFocusItemToFocusStack,
], ],
); );

View File

@ -1,4 +1,3 @@
import { FavoriteFolderHotkeyScope } from '@/favorites/constants/FavoriteFolderRightIconDropdownHotkeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@ -33,9 +32,6 @@ export const FavoriteFolderNavigationDrawerItemDropdown = ({
return ( return (
<Dropdown <Dropdown
dropdownId={`favorite-folder-edit-${folderId}`} dropdownId={`favorite-folder-edit-${folderId}`}
dropdownHotkeyScope={{
scope: FavoriteFolderHotkeyScope.FavoriteFolderRightIconDropdown,
}}
data-select-disable data-select-disable
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />

View File

@ -9,7 +9,8 @@ import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
@ -57,22 +58,23 @@ export const FavoriteFolderPicker = ({
!favoriteFoldersSearchFilter || !favoriteFoldersSearchFilter ||
'no folder'.includes(favoriteFoldersSearchFilter.toLowerCase()); 'no folder'.includes(favoriteFoldersSearchFilter.toLowerCase());
useScopedHotkeys( useHotkeysOnFocusedElement({
Key.Escape, keys: [Key.Escape],
() => { callback: () => {
if (isFavoriteFolderCreating) { if (isFavoriteFolderCreating) {
setIsFavoriteFolderCreating(false); setIsFavoriteFolderCreating(false);
return; return;
} }
onSubmit?.(); onSubmit?.();
}, },
instanceId, focusId: dropdownId,
[onSubmit, isFavoriteFolderCreating], scope: DropdownHotkeyScope.Dropdown,
); dependencies: [onSubmit, isFavoriteFolderCreating],
});
useScopedHotkeys( useHotkeysOnFocusedElement({
Key.Enter, keys: [Key.Enter],
() => { callback: () => {
if (filteredFolders.length === 1 && !showNoFolderOption) { if (filteredFolders.length === 1 && !showNoFolderOption) {
toggleFolderSelection(filteredFolders[0].id); toggleFolderSelection(filteredFolders[0].id);
onSubmit?.(); onSubmit?.();
@ -85,9 +87,15 @@ export const FavoriteFolderPicker = ({
return; return;
} }
}, },
instanceId, focusId: instanceId,
[filteredFolders, showNoFolderOption, toggleFolderSelection, onSubmit], scope: DropdownHotkeyScope.Dropdown,
); dependencies: [
filteredFolders,
showNoFolderOption,
toggleFolderSelection,
onSubmit,
],
});
return ( return (
<DropdownContent> <DropdownContent>

View File

@ -180,7 +180,6 @@ export const AdvancedFilterAddFilterRuleSelect = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={{ y: 8, x: 0 }} dropdownOffset={{ y: 8, x: 0 }}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
/> />

View File

@ -22,7 +22,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
import { FieldMetadataType } from 'twenty-shared/types'; import { FieldMetadataType } from 'twenty-shared/types';
type AdvancedFilterDropdownFilterInputProps = { type AdvancedFilterDropdownFilterInputProps = {
filterDropdownId?: string; filterDropdownId: string;
recordFilter: RecordFilter; recordFilter: RecordFilter;
}; };
@ -57,12 +57,15 @@ export const AdvancedFilterDropdownFilterInput = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}> <DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<ObjectFilterDropdownSearchInput /> <ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<ObjectFilterDropdownRecordSelect recordFilterId={recordFilter.id} /> <ObjectFilterDropdownRecordSelect
recordFilterId={recordFilter.id}
dropdownId={filterDropdownId}
/>
</DropdownContent> </DropdownContent>
)} )}
{filterType === 'ACTOR' && {filterType === 'ACTOR' &&
(isActorSourceCompositeFilter ? ( (isActorSourceCompositeFilter ? (
<ObjectFilterDropdownSourceSelect /> <ObjectFilterDropdownSourceSelect dropdownId={filterDropdownId} />
) : ( ) : (
<ObjectFilterDropdownTextInput /> <ObjectFilterDropdownTextInput />
))} ))}
@ -70,7 +73,7 @@ export const AdvancedFilterDropdownFilterInput = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}> <DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<ObjectFilterDropdownSearchInput /> <ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<ObjectFilterDropdownOptionSelect /> <ObjectFilterDropdownOptionSelect focusId={filterDropdownId} />
</DropdownContent> </DropdownContent>
)} )}
{filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />} {filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />}

View File

@ -34,7 +34,6 @@ export const AdvancedFilterFieldSelectDropdownButton = ({
recordFilterId={recordFilterId} recordFilterId={recordFilterId}
/> />
} }
dropdownHotkeyScope={{ scope: advancedFilterFieldSelectDropdownId }}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET} dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
/> />

View File

@ -23,6 +23,7 @@ import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/uti
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext'; import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel'; import { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
@ -144,9 +145,10 @@ export const AdvancedFilterFieldSelectMenu = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}> <DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<AdvancedFilterFieldSelectSearchInput /> <AdvancedFilterFieldSelectSearchInput />
<SelectableList <SelectableList
hotkeyScope={advancedFilterFieldSelectDropdownId} focusId={advancedFilterFieldSelectDropdownId}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
selectableListInstanceId={advancedFilterFieldSelectDropdownId} selectableListInstanceId={advancedFilterFieldSelectDropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{shouldShowVisibleFields && ( {shouldShowVisibleFields && (
<> <>

View File

@ -65,7 +65,6 @@ export const AdvancedFilterRecordFilterGroupOptionsDropdown = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={{ y: 2, x: 0 }} dropdownOffset={{ y: 2, x: 0 }}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
/> />

View File

@ -8,6 +8,7 @@ import { SelectControl } from '@/ui/input/components/SelectControl';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
@ -63,11 +64,12 @@ export const AdvancedFilterRecordFilterOperandSelectContent = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}> <DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<SelectableList <SelectableList
hotkeyScope={dropdownId} focusId={dropdownId}
selectableItemIdArray={operandsForFilterType.map( selectableItemIdArray={operandsForFilterType.map(
(operand) => operand, (operand) => operand,
)} )}
selectableListInstanceId={dropdownId} selectableListInstanceId={dropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{operandsForFilterType.map((filterOperand, index) => ( {operandsForFilterType.map((filterOperand, index) => (
<SelectableListItem <SelectableListItem
@ -90,7 +92,6 @@ export const AdvancedFilterRecordFilterOperandSelectContent = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET} dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
/> />

View File

@ -85,7 +85,6 @@ export const AdvancedFilterRecordFilterOptionsDropdown = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET} dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
/> />

View File

@ -18,6 +18,7 @@ import { CompositeFieldSubFieldName } from '@/settings/data-model/types/Composit
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@ -124,9 +125,10 @@ export const AdvancedFilterSubFieldSelectMenu = ({
</DropdownMenuHeader> </DropdownMenuHeader>
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<SelectableList <SelectableList
hotkeyScope={advancedFilterFieldSelectDropdownId} focusId={advancedFilterFieldSelectDropdownId}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
selectableListInstanceId={advancedFilterFieldSelectDropdownId} selectableListInstanceId={advancedFilterFieldSelectDropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{compositeFieldTypeIsFilterableByAnySubField && ( {compositeFieldTypeIsFilterableByAnySubField && (
<SelectableListItem <SelectableListItem

View File

@ -104,9 +104,11 @@ export const AdvancedFilterValueInput = ({
/> />
} }
dropdownComponents={ dropdownComponents={
<AdvancedFilterDropdownFilterInput recordFilter={recordFilter} /> <AdvancedFilterDropdownFilterInput
recordFilter={recordFilter}
filterDropdownId={dropdownId}
/>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={dropdownContentOffset} dropdownOffset={dropdownContentOffset}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
onClose={handleFilterValueDropdownClose} onClose={handleFilterValueDropdownClose}

View File

@ -13,10 +13,10 @@ import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { getDefaultSubFieldNameForCompositeFilterableFieldType } from '@/object-record/record-filter/utils/getDefaultSubFieldNameForCompositeFilterableFieldType'; import { getDefaultSubFieldNameForCompositeFilterableFieldType } from '@/object-record/record-filter/utils/getDefaultSubFieldNameForCompositeFilterableFieldType';
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { isCompositeTypeNonFilterableByAnySubField } from '@/object-record/record-filter/utils/isCompositeTypeNonFilterableByAnySubField'; import { isCompositeTypeNonFilterableByAnySubField } from '@/object-record/record-filter/utils/isCompositeTypeNonFilterableByAnySubField';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName'; import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
@ -44,7 +44,7 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
currentRecordFiltersComponentState, currentRecordFiltersComponentState,
); );
const setHotkeyScope = useSetHotkeyScope(); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const { getFieldMetadataItemById } = useGetFieldMetadataItemById(); const { getFieldMetadataItemById } = useGetFieldMetadataItemById();
@ -76,7 +76,17 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
fieldMetadataItem.type === 'RELATION' || fieldMetadataItem.type === 'RELATION' ||
fieldMetadataItem.type === 'SELECT' fieldMetadataItem.type === 'SELECT'
) { ) {
setHotkeyScope(SingleRecordPickerHotkeyScope.SingleRecordPicker); pushFocusItemToFocusStack({
focusId: fieldMetadataItem.id,
component: {
type: FocusComponentType.DROPDOWN,
instanceId: fieldMetadataItem.id,
},
hotkeyScope: {
scope: DropdownHotkeyScope.Dropdown,
},
memoizeKey: fieldMetadataItem.id,
});
} }
const filterType = getFilterTypeFromFieldType(fieldMetadataItem.type); const filterType = getFilterTypeFromFieldType(fieldMetadataItem.type);

View File

@ -3,10 +3,10 @@ import styled from '@emotion/styled';
import { useApplyObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownFilterValue'; import { useApplyObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownFilterValue';
import { useObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useObjectFilterDropdownFilterValue'; import { useObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useObjectFilterDropdownFilterValue';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { BooleanDisplay } from '@/ui/field/display/components/BooleanDisplay'; import { BooleanDisplay } from '@/ui/field/display/components/BooleanDisplay';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
@ -57,7 +57,8 @@ export const ObjectFilterDropdownBooleanSelect = () => {
<SelectableList <SelectableList
selectableListInstanceId="boolean-select" selectableListInstanceId="boolean-select"
selectableItemIdArray={options.map((option) => option.toString())} selectableItemIdArray={options.map((option) => option.toString())}
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker} focusId="boolean-select"
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
{options.map((option) => ( {options.map((option) => (

View File

@ -25,7 +25,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
type ObjectFilterDropdownFilterInputProps = { type ObjectFilterDropdownFilterInputProps = {
filterDropdownId?: string; filterDropdownId: string;
recordFilterId?: string; recordFilterId?: string;
}; };
@ -113,7 +113,10 @@ export const ObjectFilterDropdownFilterInput = ({
<> <>
<ObjectFilterDropdownSearchInput /> <ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<ObjectFilterDropdownRecordSelect recordFilterId={recordFilterId} /> <ObjectFilterDropdownRecordSelect
recordFilterId={recordFilterId}
dropdownId={filterDropdownId}
/>
</> </>
)} )}
{filterType === 'ACTOR' && <ObjectFilterDropdownTextInput />} {filterType === 'ACTOR' && <ObjectFilterDropdownTextInput />}
@ -123,7 +126,7 @@ export const ObjectFilterDropdownFilterInput = ({
<> <>
<ObjectFilterDropdownSearchInput /> <ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<ObjectFilterDropdownOptionSelect /> <ObjectFilterDropdownOptionSelect focusId={filterDropdownId} />
</> </>
)} )}
{filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />} {filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />}

View File

@ -3,7 +3,6 @@ import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenu
import { ObjectFilterDropdownOperandSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect'; import { ObjectFilterDropdownOperandSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { ClickOutsideListenerContext } from '@/ui/utilities/pointer-event/contexts/ClickOutsideListenerContext'; import { ClickOutsideListenerContext } from '@/ui/utilities/pointer-event/contexts/ClickOutsideListenerContext';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -47,9 +46,6 @@ export const ObjectFilterDropdownOperandDropdown = ({
</StyledDropdownMenuHeader> </StyledDropdownMenuHeader>
} }
dropdownComponents={<ObjectFilterDropdownOperandSelect />} dropdownComponents={<ObjectFilterDropdownOperandSelect />}
dropdownHotkeyScope={{
scope: FiltersHotkeyScope.ObjectFilterDropdownOperandDropdown,
}}
dropdownOffset={{ x: parseInt(theme.spacing(2), 10) }} dropdownOffset={{ x: parseInt(theme.spacing(2), 10) }}
/> />
</ClickOutsideListenerContext.Provider> </ClickOutsideListenerContext.Provider>

View File

@ -14,9 +14,9 @@ import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/ob
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState'; import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope'; import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
@ -30,7 +30,11 @@ type SelectOptionForFilter = FieldMetadataItemOption & {
isSelected: boolean; isSelected: boolean;
}; };
export const ObjectFilterDropdownOptionSelect = () => { export const ObjectFilterDropdownOptionSelect = ({
focusId,
}: {
focusId: string;
}) => {
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
fieldMetadataItemUsedInDropdownComponentSelector, fieldMetadataItemUsedInDropdownComponentSelector,
); );
@ -92,15 +96,16 @@ export const ObjectFilterDropdownOptionSelect = () => {
} }
}, [selectedOptions, selectOptions]); }, [selectedOptions, selectOptions]);
useScopedHotkeys( useHotkeysOnFocusedElement({
[Key.Escape], keys: [Key.Escape],
() => { callback: () => {
closeDropdown(); closeDropdown();
resetSelectedItem(); resetSelectedItem();
}, },
SingleRecordPickerHotkeyScope.SingleRecordPicker, focusId,
[closeDropdown, resetSelectedItem], scope: DropdownHotkeyScope.Dropdown,
); dependencies: [closeDropdown, resetSelectedItem],
});
const handleMultipleOptionSelectChange = ( const handleMultipleOptionSelectChange = (
optionChanged: SelectOptionForFilter, optionChanged: SelectOptionForFilter,
@ -148,7 +153,8 @@ export const ObjectFilterDropdownOptionSelect = () => {
<SelectableList <SelectableList
selectableListInstanceId={componentInstanceId} selectableListInstanceId={componentInstanceId}
selectableItemIdArray={objectRecordsIds} selectableItemIdArray={objectRecordsIds}
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker} focusId={focusId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
{showNoResult ? ( {showNoResult ? (

View File

@ -8,7 +8,6 @@ import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-recor
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown'; import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown';
import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect'; import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect';
import { SelectableItem } from '@/object-record/select/types/SelectableItem'; import { SelectableItem } from '@/object-record/select/types/SelectableItem';
@ -29,10 +28,12 @@ export const MAX_RECORDS_TO_DISPLAY = 3;
type ObjectFilterDropdownRecordSelectProps = { type ObjectFilterDropdownRecordSelectProps = {
recordFilterId?: string; recordFilterId?: string;
dropdownId: string;
}; };
export const ObjectFilterDropdownRecordSelect = ({ export const ObjectFilterDropdownRecordSelect = ({
recordFilterId, recordFilterId,
dropdownId,
}: ObjectFilterDropdownRecordSelectProps) => { }: ObjectFilterDropdownRecordSelectProps) => {
const fieldMetadataItemUsedInFilterDropdown = useRecoilComponentValueV2( const fieldMetadataItemUsedInFilterDropdown = useRecoilComponentValueV2(
fieldMetadataItemUsedInDropdownComponentSelector, fieldMetadataItemUsedInDropdownComponentSelector,
@ -220,7 +221,7 @@ export const ObjectFilterDropdownRecordSelect = ({
)} )}
<MultipleSelectDropdown <MultipleSelectDropdown
selectableListId="object-filter-record-select-id" selectableListId="object-filter-record-select-id"
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker} focusId={dropdownId}
itemsToSelect={recordsToSelect} itemsToSelect={recordsToSelect}
filteredSelectedItems={filteredSelectedRecords} filteredSelectedItems={filteredSelectedRecords}
selectedItems={selectedRecords} selectedItems={selectedRecords}

View File

@ -3,7 +3,6 @@ import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-recor
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState'; import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { getActorSourceMultiSelectOptions } from '@/object-record/object-filter-dropdown/utils/getActorSourceMultiSelectOptions'; import { getActorSourceMultiSelectOptions } from '@/object-record/object-filter-dropdown/utils/getActorSourceMultiSelectOptions';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown'; import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown';
import { SelectableItem } from '@/object-record/select/types/SelectableItem'; import { SelectableItem } from '@/object-record/select/types/SelectableItem';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
@ -15,7 +14,11 @@ import { isDefined } from 'twenty-shared/utils';
export const EMPTY_FILTER_VALUE = '[]'; export const EMPTY_FILTER_VALUE = '[]';
export const MAX_ITEMS_TO_DISPLAY = 3; export const MAX_ITEMS_TO_DISPLAY = 3;
export const ObjectFilterDropdownSourceSelect = () => { export const ObjectFilterDropdownSourceSelect = ({
dropdownId,
}: {
dropdownId: string;
}) => {
const objectFilterDropdownSearchInput = useRecoilComponentValueV2( const objectFilterDropdownSearchInput = useRecoilComponentValueV2(
objectFilterDropdownSearchInputComponentState, objectFilterDropdownSearchInputComponentState,
); );
@ -78,7 +81,7 @@ export const ObjectFilterDropdownSourceSelect = () => {
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}> <DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<MultipleSelectDropdown <MultipleSelectDropdown
selectableListId="object-filter-source-select-id" selectableListId="object-filter-source-select-id"
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker} focusId={dropdownId}
itemsToSelect={sourceTypes.filter( itemsToSelect={sourceTypes.filter(
(item) => (item) =>
!filteredSelectedItems.some((selected) => selected.id === item.id), !filteredSelectedItems.some((selected) => selected.id === item.id),

View File

@ -1,5 +0,0 @@
export enum FiltersHotkeyScope {
ObjectFilterDropdownButton = 'filter-dropdown-button',
ObjectSortDropdownButton = 'sort-dropdown-button',
ObjectFilterDropdownOperandDropdown = 'filter-dropdown-operand-dropdown',
}

View File

@ -7,7 +7,6 @@ import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dro
import { ObjectOptionsContentId } from '@/object-record/object-options-dropdown/types/ObjectOptionsContentId'; import { ObjectOptionsContentId } from '@/object-record/object-options-dropdown/types/ObjectOptionsContentId';
import { RecordGroupReorderConfirmationModal } from '@/object-record/record-group/components/RecordGroupReorderConfirmationModal'; import { RecordGroupReorderConfirmationModal } from '@/object-record/record-group/components/RecordGroupReorderConfirmationModal';
import { useRecordGroupReorderConfirmationModal } from '@/object-record/record-group/hooks/useRecordGroupReorderConfirmationModal'; import { useRecordGroupReorderConfirmationModal } from '@/object-record/record-group/hooks/useRecordGroupReorderConfirmationModal';
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton'; import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
@ -40,7 +39,6 @@ export const ObjectOptionsDropdown = ({
<> <>
<Dropdown <Dropdown
dropdownId={OBJECT_OPTIONS_DROPDOWN_ID} dropdownId={OBJECT_OPTIONS_DROPDOWN_ID}
dropdownHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
dropdownOffset={{ y: DROPDOWN_OFFSET_Y }} dropdownOffset={{ y: DROPDOWN_OFFSET_Y }}
clickableComponent={ clickableComponent={
<StyledHeaderDropdownButton isUnfolded={isDropdownOpen}> <StyledHeaderDropdownButton isUnfolded={isDropdownOpen}>

View File

@ -4,12 +4,12 @@ import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hook
import { useSetViewTypeFromLayoutOptionsMenu } from '@/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu'; import { useSetViewTypeFromLayoutOptionsMenu } from '@/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState'; import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@ -108,8 +108,9 @@ export const ObjectOptionsDropdownLayoutContent = () => {
{!!currentView && ( {!!currentView && (
<SelectableList <SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID} selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown} focusId={OBJECT_OPTIONS_DROPDOWN_ID}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<DropdownMenuItemsContainer scrollable={false}> <DropdownMenuItemsContainer scrollable={false}>
<SelectableListItem <SelectableListItem

View File

@ -2,11 +2,11 @@ import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropd
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown'; import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
import { useUpdateObjectViewOptions } from '@/object-record/object-options-dropdown/hooks/useUpdateObjectViewOptions'; import { useUpdateObjectViewOptions } from '@/object-record/object-options-dropdown/hooks/useUpdateObjectViewOptions';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState'; import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
@ -53,8 +53,9 @@ export const ObjectOptionsDropdownLayoutOpenInContent = () => {
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<SelectableList <SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID} selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown} focusId={OBJECT_OPTIONS_DROPDOWN_ID}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<SelectableListItem <SelectableListItem
itemId={ViewOpenRecordInType.SIDE_PANEL} itemId={ViewOpenRecordInType.SIDE_PANEL}

View File

@ -1,20 +1,17 @@
import { Key } from 'ts-key-enum';
import { ObjectOptionsDropdownMenuViewName } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName'; import { ObjectOptionsDropdownMenuViewName } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName';
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId'; import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard'; import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown'; import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
@ -48,14 +45,6 @@ export const ObjectOptionsDropdownMenuContent = () => {
(isDefined(currentView?.viewGroups) && currentView.viewGroups.length > 0) || (isDefined(currentView?.viewGroups) && currentView.viewGroups.length > 0) ||
currentView?.key !== 'INDEX'; currentView?.key !== 'INDEX';
useScopedHotkeys(
[Key.Escape],
() => {
closeDropdown();
},
TableOptionsHotkeyScope.Dropdown,
);
const { visibleBoardFields } = useObjectOptionsForBoard({ const { visibleBoardFields } = useObjectOptionsForBoard({
objectNameSingular: objectMetadataItem.nameSingular, objectNameSingular: objectMetadataItem.nameSingular,
recordBoardId: recordIndexId, recordBoardId: recordIndexId,
@ -101,8 +90,9 @@ export const ObjectOptionsDropdownMenuContent = () => {
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<SelectableList <SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID} selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown} focusId={OBJECT_OPTIONS_DROPDOWN_ID}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<DropdownMenuItemsContainer scrollable={false}> <DropdownMenuItemsContainer scrollable={false}>
<SelectableListItem <SelectableListItem

View File

@ -4,12 +4,13 @@ import { useUpdateObjectViewOptions } from '@/object-record/object-options-dropd
import { IconPicker } from '@/ui/input/components/IconPicker'; import { IconPicker } from '@/ui/input/components/IconPicker';
import { TextInputV2 } from '@/ui/input/components/TextInputV2'; import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { View } from '@/views/types/View'; import { View } from '@/views/types/View';
import { ViewsHotkeyScope } from '@/views/types/ViewsHotkeyScope'; import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
import { useUpdateViewFromCurrentState } from '@/views/view-picker/hooks/useUpdateViewFromCurrentState'; import { useUpdateViewFromCurrentState } from '@/views/view-picker/hooks/useUpdateViewFromCurrentState';
import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState'; import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState';
import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState'; import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState';
@ -75,17 +76,19 @@ export const ObjectOptionsDropdownMenuViewName = ({
const { updateViewFromCurrentState } = useUpdateViewFromCurrentState(); const { updateViewFromCurrentState } = useUpdateViewFromCurrentState();
const [viewName, setViewName] = useState(currentView?.name); const [viewName, setViewName] = useState(currentView?.name);
useScopedHotkeys( useHotkeysOnFocusedElement({
Key.Enter, keys: [Key.Enter],
async () => { callback: async () => {
if (viewPickerIsPersisting) { if (viewPickerIsPersisting) {
return; return;
} }
await updateViewFromCurrentState(); await updateViewFromCurrentState();
}, },
ViewsHotkeyScope.ListDropdown, focusId: VIEW_PICKER_DROPDOWN_ID,
); scope: DropdownHotkeyScope.Dropdown,
dependencies: [viewPickerIsPersisting, updateViewFromCurrentState],
});
const handleIconChange = ({ iconKey }: { iconKey: string }) => { const handleIconChange = ({ iconKey }: { iconKey: string }) => {
setViewPickerIsDirty(true); setViewPickerIsDirty(true);

View File

@ -5,11 +5,11 @@ import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hook
import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/hiddenRecordGroupIdsComponentSelector'; import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/hiddenRecordGroupIdsComponentSelector';
import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort'; import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort';
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState'; import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
@ -73,8 +73,9 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<SelectableList <SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID} selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown} focusId={OBJECT_OPTIONS_DROPDOWN_ID}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<SelectableListItem <SelectableListItem
itemId={RecordGroupSort.Manual} itemId={RecordGroupSort.Manual}

View File

@ -9,12 +9,12 @@ import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-gr
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector'; import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState'; import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState';
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState'; import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
@ -94,6 +94,8 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
'HideEmptyGroups', 'HideEmptyGroups',
]; ];
const hiddenGroupsSelectableListId = `${OBJECT_OPTIONS_DROPDOWN_ID}-hidden-groups`;
return ( return (
<DropdownContent> <DropdownContent>
<DropdownMenuHeader <DropdownMenuHeader
@ -109,8 +111,9 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<SelectableList <SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID} selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown} focusId={OBJECT_OPTIONS_DROPDOWN_ID}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{currentView?.key !== 'INDEX' && ( {currentView?.key !== 'INDEX' && (
<> <>
@ -175,9 +178,10 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItemsContainer scrollable={false}> <DropdownMenuItemsContainer scrollable={false}>
<SelectableList <SelectableList
selectableListInstanceId={`${OBJECT_OPTIONS_DROPDOWN_ID}-hidden-groups`} selectableListInstanceId={hiddenGroupsSelectableListId}
hotkeyScope={TableOptionsHotkeyScope.Dropdown} focusId={hiddenGroupsSelectableListId}
selectableItemIdArray={['HiddenGroups']} selectableItemIdArray={['HiddenGroups']}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<SelectableListItem <SelectableListItem
itemId="HiddenGroups" itemId="HiddenGroups"

View File

@ -6,7 +6,6 @@ import { OBJECT_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/co
import { useCloseSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useCloseSortDropdown'; import { useCloseSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useCloseSortDropdown';
import { useResetRecordSortDropdownSearchInput } from '@/object-record/object-sort-dropdown/hooks/useResetRecordSortDropdownSearchInput'; import { useResetRecordSortDropdownSearchInput } from '@/object-record/object-sort-dropdown/hooks/useResetRecordSortDropdownSearchInput';
import { useResetSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useResetSortDropdown'; import { useResetSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useResetSortDropdown';
import { useToggleSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useToggleSortDropdown';
import { isRecordSortDirectionDropdownMenuUnfoldedComponentState } from '@/object-record/object-sort-dropdown/states/isRecordSortDirectionDropdownMenuUnfoldedComponentState'; import { isRecordSortDirectionDropdownMenuUnfoldedComponentState } from '@/object-record/object-sort-dropdown/states/isRecordSortDirectionDropdownMenuUnfoldedComponentState';
import { objectSortDropdownSearchInputComponentState } from '@/object-record/object-sort-dropdown/states/objectSortDropdownSearchInputComponentState'; import { objectSortDropdownSearchInputComponentState } from '@/object-record/object-sort-dropdown/states/objectSortDropdownSearchInputComponentState';
import { selectedRecordSortDirectionComponentState } from '@/object-record/object-sort-dropdown/states/selectedRecordSortDirectionComponentState'; import { selectedRecordSortDirectionComponentState } from '@/object-record/object-sort-dropdown/states/selectedRecordSortDirectionComponentState';
@ -25,6 +24,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel'; import { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton'; import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
@ -86,11 +86,7 @@ export type ObjectSortDropdownButtonProps = {
hotkeyScope: HotkeyScope; hotkeyScope: HotkeyScope;
}; };
export const ObjectSortDropdownButton = ({ export const ObjectSortDropdownButton = () => {
hotkeyScope,
}: ObjectSortDropdownButtonProps) => {
const { toggleSortDropdown } = useToggleSortDropdown();
const { resetRecordSortDropdownSearchInput } = const { resetRecordSortDropdownSearchInput } =
useResetRecordSortDropdownSearchInput(); useResetRecordSortDropdownSearchInput();
@ -162,15 +158,16 @@ export const ObjectSortDropdownButton = ({
const shouldShowSeparator = const shouldShowSeparator =
visibleFieldMetadataItems.length > 0 && hiddenFieldMetadataItems.length > 0; visibleFieldMetadataItems.length > 0 && hiddenFieldMetadataItems.length > 0;
const handleButtonClick = () => {
toggleSortDropdown();
};
const handleDropdownButtonClose = () => { const handleDropdownButtonClose = () => {
resetRecordSortDropdownSearchInput(); resetRecordSortDropdownSearchInput();
resetSortDropdown(); resetSortDropdown();
}; };
const handleDropdownOpen = () => {
resetSortDropdown();
setSelectedItemId(selectableItemIdArray[0]);
};
const { closeSortDropdown } = useCloseSortDropdown(); const { closeSortDropdown } = useCloseSortDropdown();
const { upsertRecordSort } = useUpsertRecordSort(); const { upsertRecordSort } = useUpsertRecordSort();
@ -224,16 +221,10 @@ export const ObjectSortDropdownButton = ({
return ( return (
<Dropdown <Dropdown
dropdownId={OBJECT_SORT_DROPDOWN_ID} dropdownId={OBJECT_SORT_DROPDOWN_ID}
dropdownHotkeyScope={hotkeyScope}
dropdownOffset={{ y: 8 }} dropdownOffset={{ y: 8 }}
onOpen={handleDropdownOpen}
clickableComponent={ clickableComponent={
<StyledHeaderDropdownButton <StyledHeaderDropdownButton isUnfolded={isDropdownOpen}>
onClick={() => {
handleButtonClick();
setSelectedItemId(selectableItemIdArray[0]);
}}
isUnfolded={isDropdownOpen}
>
<Trans>Sort</Trans> <Trans>Sort</Trans>
</StyledHeaderDropdownButton> </StyledHeaderDropdownButton>
} }
@ -241,8 +232,9 @@ export const ObjectSortDropdownButton = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}> <DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<SelectableList <SelectableList
selectableListInstanceId={OBJECT_SORT_DROPDOWN_ID} selectableListInstanceId={OBJECT_SORT_DROPDOWN_ID}
hotkeyScope={hotkeyScope.scope}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
focusId={OBJECT_SORT_DROPDOWN_ID}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{isRecordSortDirectionMenuUnfolded && ( {isRecordSortDirectionMenuUnfolded && (
<StyledSelectedSortDirectionContainer> <StyledSelectedSortDirectionContainer>

View File

@ -9,7 +9,6 @@ import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/r
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState'; import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector'; import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard'; import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard';
import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard'; import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard';
import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody'; import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody';
@ -155,7 +154,8 @@ export const RecordBoardCard = () => {
y: event.clientY, y: event.clientY,
}); });
openDropdown(actionMenuDropdownId, { openDropdown(actionMenuDropdownId, {
scope: ActionMenuDropdownHotkeyScope.ActionMenuDropdown, enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: false,
}); });
}; };

View File

@ -5,7 +5,6 @@ import { RecordBoardColumnHeaderAggregateDropdownComponentInstanceContext } from
import { RecordBoardColumnHeaderAggregateDropdownButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownButton'; import { RecordBoardColumnHeaderAggregateDropdownButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownButton';
import { AggregateDropdownContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContent'; import { AggregateDropdownContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContent';
import { RecordBoardColumnHeaderAggregateDropdownContext } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext'; import { RecordBoardColumnHeaderAggregateDropdownContext } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
import { RecordBoardColumnHeaderAggregateContentId } from '@/object-record/record-board/types/RecordBoardColumnHeaderAggregateContentId'; import { RecordBoardColumnHeaderAggregateContentId } from '@/object-record/record-board/types/RecordBoardColumnHeaderAggregateContentId';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
@ -42,9 +41,6 @@ export const RecordBoardColumnHeaderAggregateDropdown = ({
<Dropdown <Dropdown
onClose={handleResetContent} onClose={handleResetContent}
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownHotkeyScope={{
scope: RecordBoardColumnHotkeyScope.ColumnHeader,
}}
dropdownOffset={{ y: DROPDOWN_OFFSET_Y }} dropdownOffset={{ y: DROPDOWN_OFFSET_Y }}
clickableComponent={ clickableComponent={
<RecordBoardColumnHeaderAggregateDropdownButton <RecordBoardColumnHeaderAggregateDropdownButton

View File

@ -11,6 +11,7 @@ import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldM
import { MultiSelectDisplay } from '@/ui/field/display/components/MultiSelectDisplay'; import { MultiSelectDisplay } from '@/ui/field/display/components/MultiSelectDisplay';
import { MultiSelectInput } from '@/ui/field/input/components/MultiSelectInput'; import { MultiSelectInput } from '@/ui/field/input/components/MultiSelectInput';
import { InputLabel } from '@/ui/input/components/InputLabel'; import { InputLabel } from '@/ui/input/components/InputLabel';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer'; import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString'; import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
@ -21,7 +22,6 @@ import { isDefined } from 'twenty-shared/utils';
import { VisibilityHidden } from 'twenty-ui/accessibility'; import { VisibilityHidden } from 'twenty-ui/accessibility';
import { IconChevronDown } from 'twenty-ui/display'; import { IconChevronDown } from 'twenty-ui/display';
import { SelectOption } from 'twenty-ui/input'; import { SelectOption } from 'twenty-ui/input';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
type FormMultiSelectFieldInputProps = { type FormMultiSelectFieldInputProps = {
label?: string; label?: string;
@ -256,7 +256,7 @@ export const FormMultiSelectFieldInput = ({
selectableListComponentInstanceId={ selectableListComponentInstanceId={
SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID
} }
hotkeyScope={hotkeyScope} focusId={hotkeyScope}
options={options} options={options}
onCancel={onCancel} onCancel={onCancel}
onOptionSelected={onOptionSelected} onOptionSelected={onOptionSelected}

View File

@ -10,6 +10,7 @@ import { singleRecordPickerSelectedIdComponentState } from '@/object-record/reco
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord'; import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
import { InputLabel } from '@/ui/input/components/InputLabel'; import { InputLabel } from '@/ui/input/components/InputLabel';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString'; import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
@ -18,7 +19,6 @@ import styled from '@emotion/styled';
import { useCallback, useId } from 'react'; import { useCallback, useId } from 'react';
import { isDefined, isValidUuid } from 'twenty-shared/utils'; import { isDefined, isValidUuid } from 'twenty-shared/utils';
import { IconChevronDown, IconForbid } from 'twenty-ui/display'; import { IconChevronDown, IconForbid } from 'twenty-ui/display';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
const StyledFormSelectContainer = styled(FormFieldInputInnerContainer)<{ const StyledFormSelectContainer = styled(FormFieldInputInnerContainer)<{
readonly?: boolean; readonly?: boolean;
@ -189,6 +189,7 @@ export const FormSingleRecordPicker = ({
} }
dropdownComponents={ dropdownComponents={
<SingleRecordPicker <SingleRecordPicker
focusId={dropdownId}
componentInstanceId={dropdownId} componentInstanceId={dropdownId}
EmptyIcon={IconForbid} EmptyIcon={IconForbid}
emptyLabel={'No ' + objectNameSingular} emptyLabel={'No ' + objectNameSingular}
@ -199,7 +200,6 @@ export const FormSingleRecordPicker = ({
dropdownWidth={GenericDropdownContentWidth.ExtraLarge} dropdownWidth={GenericDropdownContentWidth.ExtraLarge}
/> />
} }
dropdownHotkeyScope={{ scope: dropdownId }}
/> />
)} )}
{isDefined(VariablePicker) && !disabled && ( {isDefined(VariablePicker) && !disabled && (

View File

@ -7,6 +7,7 @@ import { getActivityTargetObjectRecords } from '@/activities/utils/getActivityTa
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useOpenRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useOpenRelationFromManyFieldInput'; 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 { 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 { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { import {
FieldMetadata, FieldMetadata,
@ -15,11 +16,13 @@ import {
} from '@/object-record/record-field/types/FieldMetadata'; } from '@/object-record/record-field/types/FieldMetadata';
import { isFieldRelationFromManyObjects } from '@/object-record/record-field/types/guards/isFieldRelationFromManyObjects'; import { isFieldRelationFromManyObjects } from '@/object-record/record-field/types/guards/isFieldRelationFromManyObjects';
import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject'; import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject';
import { getFieldInputInstanceId } from '@/object-record/record-field/utils/getFieldInputInstanceId';
import { INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY } from '@/object-record/record-inline-cell/constants/InlineCellHotkeyScopeMemoizeKey'; import { INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY } from '@/object-record/record-inline-cell/constants/InlineCellHotkeyScopeMemoizeKey';
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 { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
@ -31,7 +34,7 @@ export const useOpenFieldInputEditMode = () => {
const { openActivityTargetCellEditMode } = const { openActivityTargetCellEditMode } =
useOpenActivityTargetCellEditMode(); useOpenActivityTargetCellEditMode();
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openFieldInput = useRecoilCallback( const openFieldInput = useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
@ -72,7 +75,10 @@ export const useOpenFieldInputEditMode = () => {
}); });
openActivityTargetCellEditMode({ openActivityTargetCellEditMode({
recordPickerInstanceId: `relation-from-many-field-input-${recordId}`, recordPickerInstanceId: getRelationFromManyFieldInputInstanceId({
recordId,
fieldName: fieldDefinition.metadata.fieldName,
}),
activityTargetObjectRecords, activityTargetObjectRecords,
}); });
return; return;
@ -103,9 +109,22 @@ export const useOpenFieldInputEditMode = () => {
} }
} }
setHotkeyScopeAndMemorizePreviousScope({ pushFocusItemToFocusStack({
scope: DEFAULT_CELL_SCOPE.scope, focusId: getFieldInputInstanceId(
customScopes: DEFAULT_CELL_SCOPE.customScopes, recordId,
fieldDefinition.metadata.fieldName,
),
component: {
type: FocusComponentType.OPEN_FIELD_INPUT,
instanceId: getFieldInputInstanceId(
recordId,
fieldDefinition.metadata.fieldName,
),
},
hotkeyScope: {
scope: DEFAULT_CELL_SCOPE.scope,
customScopes: DEFAULT_CELL_SCOPE.customScopes,
},
memoizeKey: INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY, memoizeKey: INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY,
}); });
}, },
@ -113,7 +132,7 @@ export const useOpenFieldInputEditMode = () => {
openActivityTargetCellEditMode, openActivityTargetCellEditMode,
openRelationFromManyFieldInput, openRelationFromManyFieldInput,
openRelationToOneFieldInput, openRelationToOneFieldInput,
setHotkeyScopeAndMemorizePreviousScope, pushFocusItemToFocusStack,
], ],
); );

View File

@ -39,6 +39,7 @@ export const useMultiSelectField = () => {
const draftValue = useRecoilValue(getDraftValueSelector()); const draftValue = useRecoilValue(getDraftValueSelector());
return { return {
recordId,
fieldDefinition, fieldDefinition,
persistField, persistField,
fieldValues: fieldMultiSelectValues, fieldValues: fieldMultiSelectValues,

View File

@ -34,6 +34,7 @@ export const useSelectField = () => {
const draftValue = useRecoilValue(getDraftValueSelector()); const draftValue = useRecoilValue(getDraftValueSelector());
return { return {
recordId,
fieldDefinition, fieldDefinition,
persistField, persistField,
fieldValue: fieldSelectValue, fieldValue: fieldSelectValue,

View File

@ -1,6 +1,6 @@
import { useMultiSelectField } from '@/object-record/record-field/meta-types/hooks/useMultiSelectField'; import { useMultiSelectField } from '@/object-record/record-field/meta-types/hooks/useMultiSelectField';
import { SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID } from '@/object-record/record-field/meta-types/input/constants/SelectFieldInputSelectableListComponentInstanceId'; import { SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID } from '@/object-record/record-field/meta-types/input/constants/SelectFieldInputSelectableListComponentInstanceId';
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2'; import { getFieldInputInstanceId } from '@/object-record/record-field/utils/getFieldInputInstanceId';
import { MultiSelectInput } from '@/ui/field/input/components/MultiSelectInput'; import { MultiSelectInput } from '@/ui/field/input/components/MultiSelectInput';
type MultiSelectFieldInputProps = { type MultiSelectFieldInputProps = {
@ -10,14 +10,18 @@ type MultiSelectFieldInputProps = {
export const MultiSelectFieldInput = ({ export const MultiSelectFieldInput = ({
onCancel, onCancel,
}: MultiSelectFieldInputProps) => { }: MultiSelectFieldInputProps) => {
const { persistField, fieldDefinition, fieldValues } = useMultiSelectField(); const { persistField, fieldDefinition, fieldValues, recordId } =
useMultiSelectField();
return ( return (
<MultiSelectInput <MultiSelectInput
selectableListComponentInstanceId={ selectableListComponentInstanceId={
SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID
} }
hotkeyScope={DEFAULT_CELL_SCOPE.scope} focusId={getFieldInputInstanceId(
recordId,
fieldDefinition.metadata.fieldName,
)}
options={fieldDefinition.metadata.options} options={fieldDefinition.metadata.options}
onCancel={onCancel} onCancel={onCancel}
onOptionSelected={persistField} onOptionSelected={persistField}

View File

@ -10,6 +10,7 @@ import { FieldContext } from '@/object-record/record-field/contexts/FieldContext
import { useRelationField } from '@/object-record/record-field/meta-types/hooks/useRelationField'; import { useRelationField } from '@/object-record/record-field/meta-types/hooks/useRelationField';
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer'; 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 { 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 { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionComponentState';
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent'; import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
@ -25,7 +26,10 @@ export const RelationFromManyFieldInput = ({
onSubmit, onSubmit,
}: RelationFromManyFieldInputProps) => { }: RelationFromManyFieldInputProps) => {
const { fieldDefinition, recordId } = useContext(FieldContext); const { fieldDefinition, recordId } = useContext(FieldContext);
const recordPickerInstanceId = `relation-from-many-field-input-${recordId}`; const recordPickerInstanceId = getRelationFromManyFieldInputInstanceId({
recordId,
fieldName: fieldDefinition.metadata.fieldName,
});
const { updateRelation } = useUpdateRelationFromManyFieldInput(); const { updateRelation } = useUpdateRelationFromManyFieldInput();
const fieldName = fieldDefinition.metadata.fieldName; const fieldName = fieldDefinition.metadata.fieldName;
@ -84,6 +88,7 @@ export const RelationFromManyFieldInput = ({
return ( return (
<MultipleRecordPicker <MultipleRecordPicker
focusId={recordPickerInstanceId}
componentInstanceId={recordPickerInstanceId} componentInstanceId={recordPickerInstanceId}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onChange={(morphItem) => { onChange={(morphItem) => {

View File

@ -3,6 +3,7 @@ import { useRelationField } from '../../hooks/useRelationField';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer'; 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 { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionComponentState';
import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState'; import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent'; import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
@ -25,7 +26,10 @@ export const RelationToOneFieldInput = ({
const persistField = usePersistField(); const persistField = usePersistField();
const recordPickerInstanceId = `relation-to-one-field-input-${recordId}-${fieldDefinition.metadata.fieldName}`; const recordPickerInstanceId = getRelationToOneFieldInputInstanceId({
recordId,
fieldName: fieldDefinition.metadata.fieldName,
});
const handleRecordSelected = ( const handleRecordSelected = (
selectedRecord: SingleRecordPickerRecord | null | undefined, selectedRecord: SingleRecordPickerRecord | null | undefined,
@ -64,6 +68,7 @@ export const RelationToOneFieldInput = ({
return ( return (
<SingleRecordPicker <SingleRecordPicker
focusId={recordPickerInstanceId}
componentInstanceId={recordPickerInstanceId} componentInstanceId={recordPickerInstanceId}
EmptyIcon={IconForbid} EmptyIcon={IconForbid}
emptyLabel={'No ' + fieldDefinition.label} emptyLabel={'No ' + fieldDefinition.label}

View File

@ -2,6 +2,7 @@ import { useClearField } from '@/object-record/record-field/hooks/useClearField'
import { useSelectField } from '@/object-record/record-field/meta-types/hooks/useSelectField'; import { useSelectField } from '@/object-record/record-field/meta-types/hooks/useSelectField';
import { SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID } from '@/object-record/record-field/meta-types/input/constants/SelectFieldInputSelectableListComponentInstanceId'; import { SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID } from '@/object-record/record-field/meta-types/input/constants/SelectFieldInputSelectableListComponentInstanceId';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent'; import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
import { getFieldInputInstanceId } from '@/object-record/record-field/utils/getFieldInputInstanceId';
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 { SelectInput } from '@/ui/field/input/components/SelectInput'; import { SelectInput } from '@/ui/field/input/components/SelectInput';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
@ -20,7 +21,8 @@ export const SelectFieldInput = ({
onSubmit, onSubmit,
onCancel, onCancel,
}: SelectFieldInputProps) => { }: SelectFieldInputProps) => {
const { persistField, fieldDefinition, fieldValue } = useSelectField(); const { persistField, fieldDefinition, fieldValue, recordId } =
useSelectField();
const [filteredOptions, setFilteredOptions] = useState<SelectOption[]>([]); const [filteredOptions, setFilteredOptions] = useState<SelectOption[]>([]);
@ -65,7 +67,10 @@ export const SelectFieldInput = ({
SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID
} }
selectableItemIdArray={optionIds} selectableItemIdArray={optionIds}
hotkeyScope={DEFAULT_CELL_SCOPE.scope} focusId={getFieldInputInstanceId(
recordId,
fieldDefinition.metadata.fieldName,
)}
onEnter={(itemId) => { onEnter={(itemId) => {
const option = filteredOptions.find( const option = filteredOptions.find(
(option) => option.value === itemId, (option) => option.value === itemId,

View File

@ -18,9 +18,10 @@ import {
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode'; 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 { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { FieldMetadataType } from 'twenty-shared/types'; import { FieldMetadataType } from 'twenty-shared/types';
import { RelationType } from '~/generated-metadata/graphql'; import { RelationType } from '~/generated-metadata/graphql';
@ -71,7 +72,7 @@ const RelationManyFieldInputWithContext = () => {
useEffect(() => { useEffect(() => {
setRecordStoreFieldValue([]); setRecordStoreFieldValue([]);
setHotKeyScope(MultipleRecordPickerHotkeyScope.MultipleRecordPicker); setHotKeyScope(DropdownHotkeyScope.Dropdown);
openFieldInput({ openFieldInput({
fieldDefinition, fieldDefinition,
recordId: 'recordId', recordId: 'recordId',
@ -87,7 +88,10 @@ const RelationManyFieldInputWithContext = () => {
<div> <div>
<RecordFieldComponentInstanceContext.Provider <RecordFieldComponentInstanceContext.Provider
value={{ value={{
instanceId: 'relation-from-many-field-record-id-people', instanceId: getRelationFromManyFieldInputInstanceId({
recordId: 'recordId',
fieldName: 'people',
}),
}} }}
> >
<FieldContext.Provider <FieldContext.Provider

View File

@ -5,7 +5,6 @@ import { useSetRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator'; import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
@ -17,9 +16,12 @@ import {
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; 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 { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState'; import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope'; import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { FieldMetadataType } from 'twenty-shared/types'; import { FieldMetadataType } from 'twenty-shared/types';
import { getCanvasElementForDropdownTesting } from 'twenty-ui/testing'; import { getCanvasElementForDropdownTesting } from 'twenty-ui/testing';
@ -62,11 +64,30 @@ const RelationToOneFieldInputWithContext = ({
onSubmit, onSubmit,
onCancel, onCancel,
}: RelationToOneFieldInputWithContextProps) => { }: RelationToOneFieldInputWithContextProps) => {
const setHotKeyScope = useSetHotkeyScope(); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
useEffect(() => { useEffect(() => {
setHotKeyScope(SingleRecordPickerHotkeyScope.SingleRecordPicker); pushFocusItemToFocusStack({
}, [setHotKeyScope]); focusId: getRelationToOneFieldInputInstanceId({
recordId: '123',
fieldName: 'Relation',
}),
component: {
type: FocusComponentType.DROPDOWN,
instanceId: getRelationToOneFieldInputInstanceId({
recordId: '123',
fieldName: 'Relation',
}),
},
hotkeyScope: {
scope: DropdownHotkeyScope.Dropdown,
},
memoizeKey: getRelationToOneFieldInputInstanceId({
recordId: '123',
fieldName: 'Relation',
}),
});
}, [pushFocusItemToFocusStack]);
return ( return (
<div> <div>

View File

@ -1,4 +1,5 @@
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getRelationFromManyFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationFromManyFieldInputInstanceId';
import { import {
FieldRelationFromManyValue, FieldRelationFromManyValue,
FieldRelationValue, FieldRelationValue,
@ -6,17 +7,18 @@ import {
import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch'; import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch';
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState'; import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState'; import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState';
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem'; import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
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 { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
export const useOpenRelationFromManyFieldInput = () => { export const useOpenRelationFromManyFieldInput = () => {
const { performSearch } = useMultipleRecordPickerPerformSearch(); const { performSearch } = useMultipleRecordPickerPerformSearch();
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openRelationFromManyFieldInput = useRecoilCallback( const openRelationFromManyFieldInput = useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
@ -29,7 +31,10 @@ export const useOpenRelationFromManyFieldInput = () => {
objectNameSingular: string; objectNameSingular: string;
recordId: string; recordId: string;
}) => { }) => {
const recordPickerInstanceId = `relation-from-many-field-input-${recordId}`; const recordPickerInstanceId = getRelationFromManyFieldInputInstanceId({
recordId,
fieldName,
});
const fieldValue = snapshot const fieldValue = snapshot
.getLoadable<FieldRelationValue<FieldRelationFromManyValue>>( .getLoadable<FieldRelationValue<FieldRelationFromManyValue>>(
@ -88,11 +93,19 @@ export const useOpenRelationFromManyFieldInput = () => {
forcePickableMorphItems: pickableMorphItems, forcePickableMorphItems: pickableMorphItems,
}); });
setHotkeyScopeAndMemorizePreviousScope({ pushFocusItemToFocusStack({
scope: MultipleRecordPickerHotkeyScope.MultipleRecordPicker, focusId: recordPickerInstanceId,
component: {
type: FocusComponentType.DROPDOWN,
instanceId: recordPickerInstanceId,
},
hotkeyScope: {
scope: DropdownHotkeyScope.Dropdown,
},
memoizeKey: recordPickerInstanceId,
}); });
}, },
[performSearch, setHotkeyScopeAndMemorizePreviousScope], [performSearch, pushFocusItemToFocusStack],
); );
return { openRelationFromManyFieldInput }; return { openRelationFromManyFieldInput };

View File

@ -1,21 +1,26 @@
import { getRelationToOneFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationToOneFieldInputInstanceId';
import { import {
FieldRelationToOneValue, FieldRelationToOneValue,
FieldRelationValue, FieldRelationValue,
} from '@/object-record/record-field/types/FieldMetadata'; } from '@/object-record/record-field/types/FieldMetadata';
import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState'; import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
export const useOpenRelationToOneFieldInput = () => { export const useOpenRelationToOneFieldInput = () => {
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openRelationToOneFieldInput = useRecoilCallback( const openRelationToOneFieldInput = useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
({ fieldName, recordId }: { fieldName: string; recordId: string }) => { ({ fieldName, recordId }: { fieldName: string; recordId: string }) => {
const recordPickerInstanceId = `relation-to-one-field-input-${recordId}-${fieldName}`; const recordPickerInstanceId = getRelationToOneFieldInputInstanceId({
recordId,
fieldName,
});
const fieldValue = snapshot const fieldValue = snapshot
.getLoadable<FieldRelationValue<FieldRelationToOneValue>>( .getLoadable<FieldRelationValue<FieldRelationToOneValue>>(
recordStoreFamilySelector({ recordStoreFamilySelector({
@ -34,11 +39,18 @@ export const useOpenRelationToOneFieldInput = () => {
); );
} }
setHotkeyScopeAndMemorizePreviousScope({ pushFocusItemToFocusStack({
scope: SingleRecordPickerHotkeyScope.SingleRecordPicker, focusId: recordPickerInstanceId,
component: {
type: FocusComponentType.OPEN_FIELD_INPUT,
instanceId: recordPickerInstanceId,
},
// TODO: Remove this once we've fully migrated away from hotkey scopes
hotkeyScope: { scope: DropdownHotkeyScope.Dropdown },
memoizeKey: recordPickerInstanceId,
}); });
}, },
[setHotkeyScopeAndMemorizePreviousScope], [pushFocusItemToFocusStack],
); );
return { openRelationToOneFieldInput }; return { openRelationToOneFieldInput };

View File

@ -1,3 +0,0 @@
export enum RelationPickerHotkeyScope {
AddNew = 'add-new',
}

View File

@ -0,0 +1,9 @@
export const getRelationFromManyFieldInputInstanceId = ({
recordId,
fieldName,
}: {
recordId: string;
fieldName: string;
}): string => {
return `relation-from-many-field-input-${recordId}-${fieldName}`;
};

View File

@ -0,0 +1,9 @@
export const getRelationToOneFieldInputInstanceId = ({
recordId,
fieldName,
}: {
recordId: string;
fieldName: string;
}): string => {
return `relation-to-one-field-input-${recordId}-${fieldName}`;
};

View File

@ -0,0 +1,6 @@
export const getFieldInputInstanceId = (
recordId: string,
fieldName: string,
) => {
return `${recordId}-${fieldName}`;
};

View File

@ -4,7 +4,6 @@ import { MultipleRecordPickerSearchInput } from '@/object-record/record-picker/m
import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext'; import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState'; import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState'; import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState';
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId'; import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId';
import { RecordPickerLayoutDirection } from '@/object-record/record-picker/types/RecordPickerLayoutDirection'; import { RecordPickerLayoutDirection } from '@/object-record/record-picker/types/RecordPickerLayoutDirection';
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem'; import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
@ -12,10 +11,11 @@ import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObj
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton'; import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRef } from 'react'; import { useRef } from 'react';
@ -36,6 +36,7 @@ type MultipleRecordPickerProps = {
layoutDirection?: RecordPickerLayoutDirection; layoutDirection?: RecordPickerLayoutDirection;
componentInstanceId: string; componentInstanceId: string;
onClickOutside: () => void; onClickOutside: () => void;
focusId: string;
}; };
export const MultipleRecordPicker = ({ export const MultipleRecordPicker = ({
@ -45,6 +46,7 @@ export const MultipleRecordPicker = ({
onClickOutside, onClickOutside,
layoutDirection = 'search-bar-on-bottom', layoutDirection = 'search-bar-on-bottom',
componentInstanceId, componentInstanceId,
focusId,
}: MultipleRecordPickerProps) => { }: MultipleRecordPickerProps) => {
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope(); const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
@ -94,14 +96,15 @@ export const MultipleRecordPicker = ({
resetState(); resetState();
}; };
useScopedHotkeys( useHotkeysOnFocusedElement({
Key.Escape, keys: [Key.Escape],
() => { callback: () => {
handleSubmit(); handleSubmit();
}, },
MultipleRecordPickerHotkeyScope.MultipleRecordPicker, focusId,
[handleSubmit], scope: DropdownHotkeyScope.Dropdown,
); dependencies: [handleSubmit],
});
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@ -140,13 +143,19 @@ export const MultipleRecordPicker = ({
{layoutDirection === 'search-bar-on-bottom' && ( {layoutDirection === 'search-bar-on-bottom' && (
<> <>
{createNewButtonSection} {createNewButtonSection}
<MultipleRecordPickerItemsDisplay onChange={onChange} /> <MultipleRecordPickerItemsDisplay
onChange={onChange}
focusId={focusId}
/>
</> </>
)} )}
<MultipleRecordPickerSearchInput /> <MultipleRecordPickerSearchInput />
{layoutDirection === 'search-bar-on-top' && ( {layoutDirection === 'search-bar-on-top' && (
<> <>
<MultipleRecordPickerItemsDisplay onChange={onChange} /> <MultipleRecordPickerItemsDisplay
onChange={onChange}
focusId={focusId}
/>
{createNewButtonSection} {createNewButtonSection}
</> </>
)} )}

View File

@ -10,8 +10,10 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
export const MultipleRecordPickerItemsDisplay = ({ export const MultipleRecordPickerItemsDisplay = ({
onChange, onChange,
focusId,
}: { }: {
onChange?: (morphItem: RecordPickerPickableMorphItem) => void; onChange?: (morphItem: RecordPickerPickableMorphItem) => void;
focusId: string;
}) => { }) => {
const componentInstanceId = useAvailableComponentInstanceIdOrThrow( const componentInstanceId = useAvailableComponentInstanceIdOrThrow(
MultipleRecordPickerComponentInstanceContext, MultipleRecordPickerComponentInstanceContext,
@ -33,7 +35,7 @@ export const MultipleRecordPickerItemsDisplay = ({
{isLoading && itemsLength === 0 ? ( {isLoading && itemsLength === 0 ? (
<DropdownMenuSkeletonItem /> <DropdownMenuSkeletonItem />
) : ( ) : (
<MultipleRecordPickerMenuItems onChange={onChange} /> <MultipleRecordPickerMenuItems onChange={onChange} focusId={focusId} />
)} )}
<DropdownMenuSeparator /> <DropdownMenuSeparator />
</> </>

View File

@ -6,10 +6,10 @@ import { MultipleRecordPickerMenuItem } from '@/object-record/record-picker/mult
import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext'; import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState'; import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
import { multipleRecordPickerPickableRecordIdsMatchingSearchComponentSelector } from '@/object-record/record-picker/multiple-record-picker/states/selectors/multipleRecordPickerPickableRecordIdsMatchingSearchComponentSelector'; import { multipleRecordPickerPickableRecordIdsMatchingSearchComponentSelector } from '@/object-record/record-picker/multiple-record-picker/states/selectors/multipleRecordPickerPickableRecordIdsMatchingSearchComponentSelector';
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId'; import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId';
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem'; import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
@ -25,10 +25,12 @@ export const StyledSelectableItem = styled(SelectableListItem)`
type MultipleRecordPickerMenuItemsProps = { type MultipleRecordPickerMenuItemsProps = {
onChange?: (morphItem: RecordPickerPickableMorphItem) => void; onChange?: (morphItem: RecordPickerPickableMorphItem) => void;
focusId: string;
}; };
export const MultipleRecordPickerMenuItems = ({ export const MultipleRecordPickerMenuItems = ({
onChange, onChange,
focusId,
}: MultipleRecordPickerMenuItemsProps) => { }: MultipleRecordPickerMenuItemsProps) => {
const componentInstanceId = useAvailableComponentInstanceIdOrThrow( const componentInstanceId = useAvailableComponentInstanceIdOrThrow(
MultipleRecordPickerComponentInstanceContext, MultipleRecordPickerComponentInstanceContext,
@ -85,7 +87,8 @@ export const MultipleRecordPickerMenuItems = ({
<SelectableList <SelectableList
selectableListInstanceId={selectableListComponentInstanceId} selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={pickableRecordIds} selectableItemIdArray={pickableRecordIds}
hotkeyScope={MultipleRecordPickerHotkeyScope.MultipleRecordPicker} focusId={focusId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{pickableRecordIds.map((recordId) => { {pickableRecordIds.map((recordId) => {
return ( return (

View File

@ -1,3 +0,0 @@
export enum MultipleRecordPickerHotkeyScope {
MultipleRecordPicker = 'multiple-record-picker',
}

View File

@ -28,6 +28,7 @@ export const SingleRecordPicker = ({
componentInstanceId, componentInstanceId,
layoutDirection, layoutDirection,
dropdownWidth, dropdownWidth,
focusId,
}: SingleRecordPickerProps) => { }: SingleRecordPickerProps) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@ -71,6 +72,7 @@ export const SingleRecordPicker = ({
> >
<DropdownContent ref={containerRef} widthInPixels={dropdownWidth}> <DropdownContent ref={containerRef} widthInPixels={dropdownWidth}>
<SingleRecordPickerMenuItemsWithSearch <SingleRecordPickerMenuItemsWithSearch
focusId={focusId}
{...{ {...{
EmptyIcon, EmptyIcon,
emptyLabel, emptyLabel,

View File

@ -4,16 +4,16 @@ import { Key } from 'ts-key-enum';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem'; import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { SingleRecordPickerMenuItem } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItem'; import { SingleRecordPickerMenuItem } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItem';
import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext'; import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext';
import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState'; import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord'; import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
import { getSingleRecordPickerSelectableListId } from '@/object-record/record-picker/single-record-picker/utils/getSingleRecordPickerSelectableListId'; import { getSingleRecordPickerSelectableListId } from '@/object-record/record-picker/single-record-picker/utils/getSingleRecordPickerSelectableListId';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector'; import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
@ -29,7 +29,7 @@ export type SingleRecordPickerMenuItemsProps = {
onCancel?: () => void; onCancel?: () => void;
onRecordSelected: (entity?: SingleRecordPickerRecord) => void; onRecordSelected: (entity?: SingleRecordPickerRecord) => void;
selectedRecord?: SingleRecordPickerRecord; selectedRecord?: SingleRecordPickerRecord;
hotkeyScope?: string; focusId: string;
}; };
export const SingleRecordPickerMenuItems = ({ export const SingleRecordPickerMenuItems = ({
@ -40,7 +40,7 @@ export const SingleRecordPickerMenuItems = ({
onCancel, onCancel,
onRecordSelected, onRecordSelected,
selectedRecord, selectedRecord,
hotkeyScope = SingleRecordPickerHotkeyScope.SingleRecordPicker, focusId,
}: SingleRecordPickerMenuItemsProps) => { }: SingleRecordPickerMenuItemsProps) => {
const selectNone = emptyLabel const selectNone = emptyLabel
? { ? {
@ -77,15 +77,16 @@ export const SingleRecordPickerMenuItems = ({
'select-none', 'select-none',
); );
useScopedHotkeys( useHotkeysOnFocusedElement({
[Key.Escape], keys: Key.Escape,
() => { callback: () => {
resetSelectedItem(); resetSelectedItem();
onCancel?.(); onCancel?.();
}, },
hotkeyScope, focusId,
[onCancel, resetSelectedItem], scope: DropdownHotkeyScope.Dropdown,
); dependencies: [onCancel, resetSelectedItem],
});
const selectableItemIds = recordsInDropdown.map((entity) => entity.id); const selectableItemIds = recordsInDropdown.map((entity) => entity.id);
const [selectedRecordId, setSelectedRecordId] = useRecoilComponentStateV2( const [selectedRecordId, setSelectedRecordId] = useRecoilComponentStateV2(
@ -96,7 +97,8 @@ export const SingleRecordPickerMenuItems = ({
<SelectableList <SelectableList
selectableListInstanceId={selectableListComponentInstanceId} selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={selectableItemIds} selectableItemIdArray={selectableItemIds}
hotkeyScope={hotkeyScope} hotkeyScope={DropdownHotkeyScope.Dropdown}
focusId={focusId}
> >
{loading ? ( {loading ? (
<DropdownMenuSkeletonItem /> <DropdownMenuSkeletonItem />

View File

@ -26,6 +26,7 @@ export type SingleRecordPickerMenuItemsWithSearchProps = {
objectNameSingular: string; objectNameSingular: string;
recordPickerInstanceId?: string; recordPickerInstanceId?: string;
layoutDirection?: RecordPickerLayoutDirection; layoutDirection?: RecordPickerLayoutDirection;
focusId: string;
} & Pick< } & Pick<
SingleRecordPickerMenuItemsProps, SingleRecordPickerMenuItemsProps,
| 'EmptyIcon' | 'EmptyIcon'
@ -44,6 +45,7 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
onRecordSelected, onRecordSelected,
objectNameSingular, objectNameSingular,
layoutDirection = 'search-bar-on-top', layoutDirection = 'search-bar-on-top',
focusId,
}: SingleRecordPickerMenuItemsWithSearchProps) => { }: SingleRecordPickerMenuItemsWithSearchProps) => {
const { handleSearchFilterChange } = useSingleRecordPickerSearch(); const { handleSearchFilterChange } = useSingleRecordPickerSearch();
@ -96,9 +98,11 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
<DropdownMenuSeparator /> <DropdownMenuSeparator />
</> </>
)} )}
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
{searchHasNoResults && <RecordPickerNoRecordFoundMenuItem />} {searchHasNoResults && <RecordPickerNoRecordFoundMenuItem />}
<SingleRecordPickerMenuItems <SingleRecordPickerMenuItems
focusId={focusId}
recordsToSelect={records.recordsToSelect} recordsToSelect={records.recordsToSelect}
loading={records.loading} loading={records.loading}
selectedRecord={records.selectedRecords?.[0]} selectedRecord={records.selectedRecords?.[0]}
@ -123,6 +127,7 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
<SingleRecordPickerMenuItems <SingleRecordPickerMenuItems
focusId={focusId}
recordsToSelect={records.recordsToSelect} recordsToSelect={records.recordsToSelect}
loading={records.loading} loading={records.loading}
selectedRecord={records.selectedRecords?.[0]} selectedRecord={records.selectedRecords?.[0]}

View File

@ -1,3 +0,0 @@
export enum SingleRecordPickerHotkeyScope {
SingleRecordPicker = 'single-record-picker',
}

View File

@ -285,7 +285,6 @@ export const RecordDetailRelationRecordsListItem = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownScopeId }}
/> />
</DropdownScope> </DropdownScope>
)} )}

View File

@ -216,10 +216,10 @@ export const RecordDetailRelationSectionDropdown = ({
accent="tertiary" accent="tertiary"
/> />
} }
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownComponents={ dropdownComponents={
isToOneObject ? ( isToOneObject ? (
<SingleRecordPicker <SingleRecordPicker
focusId={dropdownId}
componentInstanceId={dropdownId} componentInstanceId={dropdownId}
EmptyIcon={IconForbid} EmptyIcon={IconForbid}
onRecordSelected={handleRelationPickerEntitySelected} onRecordSelected={handleRelationPickerEntitySelected}
@ -235,6 +235,7 @@ export const RecordDetailRelationSectionDropdown = ({
/> />
) : ( ) : (
<MultipleRecordPicker <MultipleRecordPicker
focusId={dropdownId}
componentInstanceId={dropdownId} componentInstanceId={dropdownId}
onCreate={() => { onCreate={() => {
closeDropdown(); closeDropdown();

View File

@ -8,6 +8,7 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode'; import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { useResetFocusStack } from '@/ui/utilities/focus/hooks/useResetFocusStack';
export const useCloseRecordTableCellInGroup = () => { export const useCloseRecordTableCellInGroup = () => {
const { recordTableId } = useRecordTableContextOrThrow(); const { recordTableId } = useRecordTableContextOrThrow();
@ -22,11 +23,17 @@ export const useCloseRecordTableCellInGroup = () => {
const closeCurrentTableCellInEditMode = const closeCurrentTableCellInEditMode =
useCloseCurrentTableCellInEditMode(recordTableId); useCloseCurrentTableCellInEditMode(recordTableId);
const { resetFocusStack } = useResetFocusStack();
const closeTableCellInGroup = useRecoilCallback( const closeTableCellInGroup = useRecoilCallback(
() => () => { () => () => {
toggleClickOutside(true); toggleClickOutside(true);
setDragSelectionStartEnabled(true); setDragSelectionStartEnabled(true);
closeCurrentTableCellInEditMode(); closeCurrentTableCellInEditMode();
// TODO: Remove this once we've fully migrated away from hotkey scopes
resetFocusStack();
setHotkeyScope(TableHotkeyScope.TableFocus, { setHotkeyScope(TableHotkeyScope.TableFocus, {
goto: true, goto: true,
keyboardShortcutMenu: true, keyboardShortcutMenu: true,
@ -35,6 +42,7 @@ export const useCloseRecordTableCellInGroup = () => {
}, },
[ [
closeCurrentTableCellInEditMode, closeCurrentTableCellInEditMode,
resetFocusStack,
setDragSelectionStartEnabled, setDragSelectionStartEnabled,
setHotkeyScope, setHotkeyScope,
toggleClickOutside, toggleClickOutside,

View File

@ -6,6 +6,7 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode'; import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { useResetFocusStack } from '@/ui/utilities/focus/hooks/useResetFocusStack';
import { useCallback } from 'react'; import { useCallback } from 'react';
export const useCloseRecordTableCellNoGroup = () => { export const useCloseRecordTableCellNoGroup = () => {
@ -22,10 +23,16 @@ export const useCloseRecordTableCellNoGroup = () => {
const closeCurrentTableCellInEditMode = const closeCurrentTableCellInEditMode =
useCloseCurrentTableCellInEditMode(recordTableId); useCloseCurrentTableCellInEditMode(recordTableId);
const { resetFocusStack } = useResetFocusStack();
const closeTableCellNoGroup = useCallback(() => { const closeTableCellNoGroup = useCallback(() => {
toggleClickOutside(true); toggleClickOutside(true);
setDragSelectionStartEnabled(true); setDragSelectionStartEnabled(true);
closeCurrentTableCellInEditMode(); closeCurrentTableCellInEditMode();
// TODO: Remove this once we've fully migrated away from hotkey scopes
resetFocusStack();
setHotkeyScope(TableHotkeyScope.TableFocus, { setHotkeyScope(TableHotkeyScope.TableFocus, {
goto: true, goto: true,
keyboardShortcutMenu: true, keyboardShortcutMenu: true,
@ -33,6 +40,7 @@ export const useCloseRecordTableCellNoGroup = () => {
}); });
}, [ }, [
closeCurrentTableCellInEditMode, closeCurrentTableCellInEditMode,
resetFocusStack,
setDragSelectionStartEnabled, setDragSelectionStartEnabled,
setHotkeyScope, setHotkeyScope,
toggleClickOutside, toggleClickOutside,

View File

@ -2,7 +2,6 @@ import { useRecoilCallback } from 'recoil';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState'; import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId'; import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState'; import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
@ -58,9 +57,7 @@ export const useTriggerActionMenuDropdown = ({
closeCommandMenu(); closeCommandMenu();
openDropdown({ openDropdown();
scope: ActionMenuDropdownHotkeyScope.ActionMenuDropdown,
});
}, },
[ [
recordIndexActionMenuDropdownPositionState, recordIndexActionMenuDropdownPositionState,

View File

@ -76,7 +76,6 @@ export const RecordTableColumnFooterWithDropdown = ({
} }
dropdownOffset={{ x: -1 }} dropdownOffset={{ x: -1 }}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownHotkeyScope={{ scope: dropdownId }}
/> />
); );
}; };

View File

@ -35,7 +35,6 @@ export const RecordTableColumnHeadWithDropdown = ({
dropdownComponents={<RecordTableColumnHeadDropdownMenu column={column} />} dropdownComponents={<RecordTableColumnHeadDropdownMenu column={column} />}
dropdownOffset={{ x: -1 }} dropdownOffset={{ x: -1 }}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownHotkeyScope={{ scope: column.fieldMetadataId + '-header' }}
/> />
); );
}; };

View File

@ -50,9 +50,6 @@ const StyledDropdownContainer = styled.div`
cursor: pointer; cursor: pointer;
`; `;
const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID =
'hidden-table-columns-dropdown-hotkey-scope-id';
export const RecordTableHeaderLastColumn = () => { export const RecordTableHeaderLastColumn = () => {
const theme = useTheme(); const theme = useTheme();
@ -89,9 +86,6 @@ export const RecordTableHeaderLastColumn = () => {
} }
dropdownComponents={<RecordTableHeaderPlusButtonContent />} dropdownComponents={<RecordTableHeaderPlusButtonContent />}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownHotkeyScope={{
scope: HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID,
}}
/> />
</StyledDropdownContainer> </StyledDropdownContainer>
</StyledPlusIconHeaderCell> </StyledPlusIconHeaderCell>

View File

@ -3,19 +3,20 @@ import { Key } from 'ts-key-enum';
import { SelectableItem } from '@/object-record/select/types/SelectableItem'; import { SelectableItem } from '@/object-record/select/types/SelectableItem';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem'; import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { Avatar } from 'twenty-ui/display'; import { Avatar } from 'twenty-ui/display';
import { MenuItem, MenuItemMultiSelectAvatar } from 'twenty-ui/navigation'; import { MenuItem, MenuItemMultiSelectAvatar } from 'twenty-ui/navigation';
export const MultipleSelectDropdown = ({ export const MultipleSelectDropdown = ({
selectableListId, selectableListId,
hotkeyScope, focusId,
itemsToSelect, itemsToSelect,
loadingItems, loadingItems,
filteredSelectedItems, filteredSelectedItems,
@ -23,7 +24,7 @@ export const MultipleSelectDropdown = ({
searchFilter, searchFilter,
}: { }: {
selectableListId: string; selectableListId: string;
hotkeyScope: string; focusId: string;
itemsToSelect: SelectableItem[]; itemsToSelect: SelectableItem[];
filteredSelectedItems: SelectableItem[]; filteredSelectedItems: SelectableItem[];
selectedItems: SelectableItem[]; selectedItems: SelectableItem[];
@ -61,15 +62,16 @@ export const MultipleSelectDropdown = ({
...(itemsToSelect ?? []), ...(itemsToSelect ?? []),
]; ];
useScopedHotkeys( useHotkeysOnFocusedElement({
[Key.Escape], keys: [Key.Escape],
() => { callback: () => {
closeDropdown(); closeDropdown();
resetSelectedItem(); resetSelectedItem();
}, },
hotkeyScope, focusId,
[closeDropdown, resetSelectedItem], scope: DropdownHotkeyScope.Dropdown,
); dependencies: [closeDropdown, resetSelectedItem],
});
const showNoResult = const showNoResult =
itemsToSelect?.length === 0 && itemsToSelect?.length === 0 &&
@ -83,7 +85,8 @@ export const MultipleSelectDropdown = ({
<SelectableList <SelectableList
selectableListInstanceId={selectableListId} selectableListInstanceId={selectableListId}
selectableItemIdArray={selectableItemIds} selectableItemIdArray={selectableItemIds}
hotkeyScope={hotkeyScope} focusId={focusId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
{itemsInDropdown?.map((item) => { {itemsInDropdown?.map((item) => {

View File

@ -51,7 +51,6 @@ export const SettingsAccountsRowDropdownMenu = ({
<Dropdown <Dropdown
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownPlacement="right-start" dropdownPlacement="right-start"
dropdownHotkeyScope={{ scope: dropdownId }}
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />
} }

View File

@ -2,7 +2,6 @@ import { Select } from '@/ui/input/components/Select';
import { SelectControl } from '@/ui/input/components/SelectControl'; import { SelectControl } from '@/ui/input/components/SelectControl';
import { TextArea } from '@/ui/input/components/TextArea'; import { TextArea } from '@/ui/input/components/TextArea';
import { TextInputV2 } from '@/ui/input/components/TextInputV2'; import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { SelectHotkeyScope } from '@/ui/input/types/SelectHotkeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@ -96,7 +95,6 @@ export const ConfigVariableDatabaseInput = ({
{options && Array.isArray(options) ? ( {options && Array.isArray(options) ? (
<Dropdown <Dropdown
dropdownId="config-variable-array-dropdown" dropdownId="config-variable-array-dropdown"
dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownOffset={{ dropdownOffset={{
y: 8, y: 8,

View File

@ -46,7 +46,6 @@ export const ConfigVariableFilterDropdown = ({
/> />
} }
dropdownId="env-var-options-dropdown" dropdownId="env-var-options-dropdown"
dropdownHotkeyScope={{ scope: 'env-var-options' }}
dropdownOffset={{ x: 0, y: 10 }} dropdownOffset={{ x: 0, y: 10 }}
dropdownComponents={ dropdownComponents={
<ConfigVariableOptionsDropdownContent <ConfigVariableOptionsDropdownContent

View File

@ -130,7 +130,6 @@ export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
/> />
</StyledContainer> </StyledContainer>
); );

View File

@ -117,7 +117,6 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
<Dropdown <Dropdown
dropdownId={SELECT_COLOR_DROPDOWN_ID} dropdownId={SELECT_COLOR_DROPDOWN_ID}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownHotkeyScope={{ scope: SELECT_COLOR_DROPDOWN_ID }}
clickableComponent={<StyledColorSample colorName={option.color} />} clickableComponent={<StyledColorSample colorName={option.color} />}
dropdownComponents={ dropdownComponents={
<DropdownContent> <DropdownContent>
@ -160,7 +159,6 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
<Dropdown <Dropdown
dropdownId={SELECT_ACTIONS_DROPDOWN_ID} dropdownId={SELECT_ACTIONS_DROPDOWN_ID}
dropdownPlacement="right-start" dropdownPlacement="right-start"
dropdownHotkeyScope={{ scope: SELECT_ACTIONS_DROPDOWN_ID }}
clickableComponent={ clickableComponent={
<StyledLightIconButton accent="tertiary" Icon={IconDotsVertical} /> <StyledLightIconButton accent="tertiary" Icon={IconDotsVertical} />
} }

View File

@ -82,7 +82,6 @@ export const SettingsObjectFieldActiveActionDropdown = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
/> />
); );
}; };

View File

@ -86,9 +86,6 @@ export const SettingsObjectFieldInactiveActionDropdown = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{
scope: dropdownId,
}}
/> />
); );
}; };

View File

@ -63,7 +63,6 @@ export const SettingsObjectInactiveMenuDropDown = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
/> />
); );
}; };

View File

@ -54,7 +54,6 @@ export const SettingsIntegrationDatabaseConnectionSummaryCard = ({
/> />
<Dropdown <Dropdown
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownHotkeyScope={{ scope: dropdownId }}
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />
} }

View File

@ -238,7 +238,6 @@ export const SettingsRoleAssignment = ({
<StyledAssignToMemberContainer> <StyledAssignToMemberContainer>
<Dropdown <Dropdown
dropdownId="role-member-select" dropdownId="role-member-select"
dropdownHotkeyScope={{ scope: 'roleAssignment' }}
dropdownOffset={{ x: 0, y: 4 }} dropdownOffset={{ x: 0, y: 4 }}
clickableComponent={ clickableComponent={
<> <>

View File

@ -69,7 +69,6 @@ export const SettingsSecuritySSORowDropdownMenu = ({
<Dropdown <Dropdown
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownPlacement="right-start" dropdownPlacement="right-start"
dropdownHotkeyScope={{ scope: dropdownId }}
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />
} }

View File

@ -58,7 +58,6 @@ export const SettingsSecurityApprovedAccessDomainRowDropdownMenu = ({
<Dropdown <Dropdown
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownPlacement="right-start" dropdownPlacement="right-start"
dropdownHotkeyScope={{ scope: dropdownId }}
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />
} }

View File

@ -131,7 +131,6 @@ export const SettingsServerlessFunctionTabEnvironmentVariableTableRow = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropDownId }}
/> />
</TableCell> </TableCell>
</StyledTableRow> </StyledTableRow>

View File

@ -123,9 +123,6 @@ export const MatchColumnToFieldSelect = ({
return ( return (
<Dropdown <Dropdown
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownHotkeyScope={{
scope: dropdownId,
}}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
clickableComponent={ clickableComponent={
<StyledMenuItem <StyledMenuItem

View File

@ -52,7 +52,6 @@ export const SubMatchingSelectRowRightDropdown = <T extends string>({
return ( return (
<StyledDropdownContainer> <StyledDropdownContainer>
<Dropdown <Dropdown
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
clickableComponent={ clickableComponent={

View File

@ -44,7 +44,6 @@ export const SupportDropdown = () => {
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
/> />
); );
}; };

View File

@ -2,15 +2,17 @@ import { useRef, useState } from 'react';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata';
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; 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 { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
@ -21,7 +23,7 @@ import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmp
type MultiSelectInputProps = { type MultiSelectInputProps = {
selectableListComponentInstanceId: string; selectableListComponentInstanceId: string;
values: FieldMultiSelectValue; values: FieldMultiSelectValue;
hotkeyScope: string; focusId: string;
onCancel?: () => void; onCancel?: () => void;
options: SelectOption[]; options: SelectOption[];
onOptionSelected: (value: FieldMultiSelectValue) => void; onOptionSelected: (value: FieldMultiSelectValue) => void;
@ -32,7 +34,7 @@ export const MultiSelectInput = ({
selectableListComponentInstanceId, selectableListComponentInstanceId,
values, values,
options, options,
hotkeyScope, focusId,
onCancel, onCancel,
onOptionSelected, onOptionSelected,
dropdownWidth, dropdownWidth,
@ -69,15 +71,16 @@ export const MultiSelectInput = ({
} }
}; };
useScopedHotkeys( useHotkeysOnFocusedElement({
Key.Escape, keys: Key.Escape,
() => { callback: () => {
onCancel?.(); onCancel?.();
resetSelectedItem(); resetSelectedItem();
}, },
hotkeyScope, focusId,
[onCancel, resetSelectedItem], scope: DEFAULT_CELL_SCOPE.scope,
); dependencies: [onCancel, resetSelectedItem],
});
useListenClickOutside({ useListenClickOutside({
refs: [containerRef], refs: [containerRef],
@ -102,7 +105,8 @@ export const MultiSelectInput = ({
<SelectableList <SelectableList
selectableListInstanceId={selectableListComponentInstanceId} selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={optionIds} selectableItemIdArray={optionIds}
hotkeyScope={hotkeyScope} focusId={focusId}
hotkeyScope={DEFAULT_CELL_SCOPE.scope}
> >
<DropdownContent <DropdownContent
ref={containerRef} ref={containerRef}
@ -122,17 +126,25 @@ export const MultiSelectInput = ({
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
{filteredOptionsInDropDown.map((option) => { {filteredOptionsInDropDown.map((option) => {
return ( return (
<MenuItemMultiSelectTag <SelectableListItem
key={option.value} key={option.value}
selected={values?.includes(option.value) || false} itemId={option.value}
text={option.label} onEnter={() => {
color={option.color ?? 'transparent'} onOptionSelected(formatNewSelectedOptions(option.value));
Icon={option.Icon ?? undefined} }}
onClick={() => >
onOptionSelected(formatNewSelectedOptions(option.value)) <MenuItemMultiSelectTag
} key={option.value}
isKeySelected={selectedItemId === option.value} selected={values?.includes(option.value) || false}
/> text={option.label}
color={option.color ?? 'transparent'}
Icon={option.Icon ?? undefined}
onClick={() =>
onOptionSelected(formatNewSelectedOptions(option.value))
}
isKeySelected={selectedItemId === option.value}
/>
</SelectableListItem>
); );
})} })}
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>

View File

@ -1,3 +1,4 @@
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { SelectInput as SelectBaseInput } from '@/ui/input/components/SelectInput'; import { SelectInput as SelectBaseInput } from '@/ui/input/components/SelectInput';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectOption } from 'twenty-ui/input'; import { SelectOption } from 'twenty-ui/input';
@ -5,7 +6,7 @@ import { SelectOption } from 'twenty-ui/input';
type SelectInputProps = { type SelectInputProps = {
selectableListComponentInstanceId: string; selectableListComponentInstanceId: string;
selectableItemIdArray: string[]; selectableItemIdArray: string[];
hotkeyScope: string; focusId: string;
onEnter: (itemId: string) => void; onEnter: (itemId: string) => void;
onOptionSelected: (selectedOption: SelectOption) => void; onOptionSelected: (selectedOption: SelectOption) => void;
options: SelectOption[]; options: SelectOption[];
@ -19,7 +20,7 @@ type SelectInputProps = {
export const SelectInput = ({ export const SelectInput = ({
selectableListComponentInstanceId, selectableListComponentInstanceId,
selectableItemIdArray, selectableItemIdArray,
hotkeyScope, focusId,
onOptionSelected, onOptionSelected,
options, options,
onCancel, onCancel,
@ -32,7 +33,8 @@ export const SelectInput = ({
<SelectableList <SelectableList
selectableListInstanceId={selectableListComponentInstanceId} selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={hotkeyScope} focusId={focusId}
hotkeyScope={DEFAULT_CELL_SCOPE.scope}
> >
<SelectBaseInput <SelectBaseInput
onOptionSelected={onOptionSelected} onOptionSelected={onOptionSelected}
@ -42,7 +44,7 @@ export const SelectInput = ({
onFilterChange={onFilterChange} onFilterChange={onFilterChange}
onClear={onClear} onClear={onClear}
clearLabel={clearLabel} clearLabel={clearLabel}
hotkeyScope={hotkeyScope} focusId={focusId}
/> />
</SelectableList> </SelectableList>
); );

View File

@ -12,6 +12,7 @@ import { arrayToChunks } from '~/utils/array/arrayToChunks';
import { ICON_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/ui/input/components/constants/IconPickerDropdownContentWidth'; import { ICON_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/ui/input/components/constants/IconPickerDropdownContentWidth';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useSelectableListListenToEnterHotkeyOnItem } from '@/ui/layout/selectable-list/hooks/useSelectableListListenToEnterHotkeyOnItem'; import { useSelectableListListenToEnterHotkeyOnItem } from '@/ui/layout/selectable-list/hooks/useSelectableListListenToEnterHotkeyOnItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -71,9 +72,10 @@ const IconPickerIcon = ({
); );
useSelectableListListenToEnterHotkeyOnItem({ useSelectableListListenToEnterHotkeyOnItem({
hotkeyScope: IconPickerHotkeyScope.IconPicker, focusId: iconKey,
itemId: iconKey, itemId: iconKey,
onEnter: onClick, onEnter: onClick,
hotkeyScope: DropdownHotkeyScope.Dropdown,
}); });
return ( return (
@ -184,7 +186,6 @@ export const IconPicker = ({
<div className={className}> <div className={className}>
<Dropdown <Dropdown
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownHotkeyScope={{ scope: IconPickerHotkeyScope.IconPicker }}
clickableComponent={ clickableComponent={
<IconButton <IconButton
ariaLabel={`Click to select icon ${ ariaLabel={`Click to select icon ${
@ -203,7 +204,8 @@ export const IconPicker = ({
<SelectableList <SelectableList
selectableListInstanceId="icon-list" selectableListInstanceId="icon-list"
selectableItemIdMatrix={iconKeys2d} selectableItemIdMatrix={iconKeys2d}
hotkeyScope={IconPickerHotkeyScope.IconPicker} focusId={dropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<DropdownMenuSearchInput <DropdownMenuSearchInput
placeholder={t`Search icon`} placeholder={t`Search icon`}

View File

@ -9,6 +9,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectControl } from '@/ui/input/components/SelectControl'; import { SelectControl } from '@/ui/input/components/SelectControl';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset'; import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
@ -19,7 +20,6 @@ import { isDefined } from 'twenty-shared/utils';
import { IconComponent } from 'twenty-ui/display'; import { IconComponent } from 'twenty-ui/display';
import { SelectOption } from 'twenty-ui/input'; import { SelectOption } from 'twenty-ui/input';
import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation'; import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation';
import { SelectHotkeyScope } from '../types/SelectHotkeyScope';
export type SelectSizeVariant = 'small' | 'default'; export type SelectSizeVariant = 'small' | 'default';
@ -166,9 +166,10 @@ export const Select = <Value extends SelectValue>({
{!!filteredOptions.length && ( {!!filteredOptions.length && (
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
<SelectableList <SelectableList
hotkeyScope={SelectHotkeyScope.Select}
selectableListInstanceId={dropdownId} selectableListInstanceId={dropdownId}
focusId={dropdownId}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{filteredOptions.map((option) => ( {filteredOptions.map((option) => (
<SelectableListItem <SelectableListItem
@ -211,7 +212,6 @@ export const Select = <Value extends SelectValue>({
)} )}
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }}
/> />
)} )}
</StyledContainer> </StyledContainer>

View File

@ -2,10 +2,13 @@ import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { Key } from 'ts-key-enum';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { TagColor } from 'twenty-ui/components'; import { TagColor } from 'twenty-ui/components';
import { SelectOption } from 'twenty-ui/input'; import { SelectOption } from 'twenty-ui/input';
@ -19,7 +22,7 @@ interface SelectInputProps {
onFilterChange?: (filteredOptions: SelectOption[]) => void; onFilterChange?: (filteredOptions: SelectOption[]) => void;
onClear?: () => void; onClear?: () => void;
clearLabel?: string; clearLabel?: string;
hotkeyScope: string; focusId: string;
} }
export const SelectInput = ({ export const SelectInput = ({
@ -30,10 +33,19 @@ export const SelectInput = ({
onCancel, onCancel,
defaultOption, defaultOption,
onFilterChange, onFilterChange,
hotkeyScope,
}: SelectInputProps) => { }: SelectInputProps) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
// Get the SelectableList instance id from context
const selectableListInstanceId = useAvailableComponentInstanceIdOrThrow(
SelectableListComponentInstanceContext,
);
const selectedItemId = useRecoilComponentValueV2(
selectedItemIdComponentState,
selectableListInstanceId,
);
const [searchFilter, setSearchFilter] = useState(''); const [searchFilter, setSearchFilter] = useState('');
const [selectedOption, setSelectedOption] = useState< const [selectedOption, setSelectedOption] = useState<
SelectOption | undefined SelectOption | undefined
@ -61,6 +73,11 @@ export const SelectInput = ({
onOptionSelected(option); onOptionSelected(option);
}; };
const handleClearOption = () => {
setSelectedOption(undefined);
onClear?.();
};
useEffect(() => { useEffect(() => {
onFilterChange?.(optionsInDropDown); onFilterChange?.(optionsInDropDown);
}, [onFilterChange, optionsInDropDown]); }, [onFilterChange, optionsInDropDown]);
@ -81,20 +98,6 @@ export const SelectInput = ({
listenerId: 'select-input', listenerId: 'select-input',
}); });
useScopedHotkeys(
Key.Enter,
() => {
const selectedOption = optionsInDropDown.find((option) =>
option.label.toLowerCase().includes(searchFilter.toLowerCase()),
);
if (isDefined(selectedOption)) {
handleOptionChange(selectedOption);
}
},
hotkeyScope,
[searchFilter, optionsInDropDown],
);
return ( return (
<DropdownContent ref={containerRef} selectDisabled> <DropdownContent ref={containerRef} selectDisabled>
<DropdownMenuSearchInput <DropdownMenuSearchInput
@ -105,27 +108,37 @@ export const SelectInput = ({
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
{onClear && clearLabel && ( {onClear && clearLabel && (
<MenuItemSelectTag <SelectableListItem
key={`No ${clearLabel}`} itemId={`No ${clearLabel}`}
text={`No ${clearLabel}`} onEnter={handleClearOption}
color="transparent" >
variant={'outline'} <MenuItemSelectTag
onClick={() => { key={`No ${clearLabel}`}
setSelectedOption(undefined); text={`No ${clearLabel}`}
onClear(); color="transparent"
}} variant={'outline'}
/> onClick={handleClearOption}
isKeySelected={selectedItemId === `No ${clearLabel}`}
/>
</SelectableListItem>
)} )}
{optionsInDropDown.map((option) => { {optionsInDropDown.map((option) => {
return ( return (
<MenuItemSelectTag <SelectableListItem
key={option.value} key={option.value}
focused={selectedOption?.value === option.value} itemId={option.value}
text={option.label} onEnter={() => handleOptionChange(option)}
color={(option.color as TagColor) ?? 'transparent'} >
onClick={() => handleOptionChange(option)} <MenuItemSelectTag
LeftIcon={option.Icon} key={option.value}
/> selected={selectedOption?.value === option.value}
text={option.label}
color={(option.color as TagColor) ?? 'transparent'}
onClick={() => handleOptionChange(option)}
LeftIcon={option.Icon}
isKeySelected={selectedItemId === option.value}
/>
</SelectableListItem>
); );
})} })}
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>

View File

@ -70,7 +70,6 @@ export const CurrencyPickerDropdownButton = ({
return ( return (
<Dropdown <Dropdown
dropdownId="currency-picker-dropdown-id" dropdownId="currency-picker-dropdown-id"
dropdownHotkeyScope={{ scope: CurrencyPickerHotkeyScope.CurrencyPicker }}
clickableComponent={ clickableComponent={
<StyledDropdownButtonContainer> <StyledDropdownButtonContainer>
<StyledIconContainer> <StyledIconContainer>

View File

@ -6,8 +6,6 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { CountryPickerHotkeyScope } from '../types/CountryPickerHotkeyScope';
import { PhoneCountryPickerDropdownSelect } from './PhoneCountryPickerDropdownSelect'; import { PhoneCountryPickerDropdownSelect } from './PhoneCountryPickerDropdownSelect';
import 'react-phone-number-input/style.css'; import 'react-phone-number-input/style.css';
@ -98,7 +96,6 @@ export const PhoneCountryPickerDropdownButton = ({
return ( return (
<Dropdown <Dropdown
dropdownId="country-picker-dropdown-id" dropdownId="country-picker-dropdown-id"
dropdownHotkeyScope={{ scope: CountryPickerHotkeyScope.CountryPicker }}
clickableComponent={ clickableComponent={
<StyledDropdownButtonContainer isUnfolded={isDropdownOpen}> <StyledDropdownButtonContainer isUnfolded={isDropdownOpen}>
<StyledIconContainer> <StyledIconContainer>

Some files were not shown because too many files have changed in this diff Show More