Replace hotkey scopes by focus stack (Part 6 - Remove Hotkey scopes 🫳🎤) (#13127)

# Replace hotkey scopes by focus stack (Part 6 - Remove Hotkey scopes)

This PR is the last part of a refactoring aiming to deprecate the hotkey
scopes api in favor of the new focus stack api which is more robust.
Part 1: https://github.com/twentyhq/twenty/pull/12673
Part 2: https://github.com/twentyhq/twenty/pull/12798
Part 3: https://github.com/twentyhq/twenty/pull/12910
Part 4: https://github.com/twentyhq/twenty/pull/12933
Part 5: https://github.com/twentyhq/twenty/pull/13106

In this part, we completely remove the hotkey scopes.
This commit is contained in:
Raphaël Bosi
2025-07-09 17:21:14 +02:00
committed by GitHub
parent 0a7b21234b
commit eba997be98
215 changed files with 687 additions and 1424 deletions
@@ -11,7 +11,6 @@ import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useLis
import { useRef } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { Button } from 'twenty-ui/input';
import { DialogHotkeyScope } from '../types/DialogHotkeyScope';
const StyledDialogOverlay = styled(motion.div)`
align-items: center;
@@ -116,7 +115,6 @@ export const Dialog = ({
keys: [Key.Enter],
callback: handleEnter,
focusId: DIALOG_FOCUS_ID,
scope: DialogHotkeyScope.Dialog,
dependencies: [buttons],
});
@@ -124,7 +122,6 @@ export const Dialog = ({
keys: [Key.Escape],
callback: handleEscape,
focusId: DIALOG_FOCUS_ID,
scope: DialogHotkeyScope.Dialog,
dependencies: [handleEscape],
});
@@ -1,8 +1,6 @@
import { useEffect } from 'react';
import { DIALOG_FOCUS_ID } from '@/ui/feedback/dialog-manager/constants/DialogFocusId';
import { DIALOG_MANAGER_HOTKEY_SCOPE_MEMOIZE_KEY } from '@/ui/feedback/dialog-manager/constants/DialogManagerHotkeyScopeMemoizeKey';
import { DialogHotkeyScope } from '@/ui/feedback/dialog-manager/types/DialogHotkeyScope';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useDialogManagerScopedStates } from '../hooks/internal/useDialogManagerScopedStates';
@@ -23,10 +21,6 @@ export const DialogManagerEffect = () => {
type: FocusComponentType.DIALOG,
instanceId: DIALOG_FOCUS_ID,
},
hotkeyScope: {
scope: DialogHotkeyScope.Dialog,
},
memoizeKey: DIALOG_MANAGER_HOTKEY_SCOPE_MEMOIZE_KEY,
});
}, [dialogInternal.queue, pushFocusItemToFocusStack]);
@@ -1,3 +0,0 @@
export enum DialogHotkeyScope {
Dialog = 'dialog',
}
@@ -57,7 +57,6 @@ export type AddressInputProps = {
event: MouseEvent | TouchEvent,
newAddress: FieldAddressDraftValue,
) => void;
hotkeyScope: string;
clearable?: boolean;
onChange?: (updatedValue: FieldAddressDraftValue) => void;
};
@@ -65,7 +64,6 @@ export type AddressInputProps = {
export const AddressInput = ({
instanceId,
value,
hotkeyScope,
onTab,
onShiftTab,
onEnter,
@@ -154,7 +152,6 @@ export const AddressInput = ({
useHotkeysOnFocusedElement({
keys: ['tab'],
callback: handleTab,
scope: hotkeyScope,
focusId: instanceId,
dependencies: [handleTab],
});
@@ -162,7 +159,6 @@ export const AddressInput = ({
useHotkeysOnFocusedElement({
keys: ['shift+tab'],
callback: handleShiftTab,
scope: hotkeyScope,
focusId: instanceId,
dependencies: [handleShiftTab],
});
@@ -172,7 +168,6 @@ export const AddressInput = ({
callback: () => {
onEnter(internalValue);
},
scope: hotkeyScope,
focusId: instanceId,
dependencies: [onEnter, internalValue],
});
@@ -182,7 +177,6 @@ export const AddressInput = ({
callback: () => {
onEscape(internalValue);
},
scope: hotkeyScope,
focusId: instanceId,
dependencies: [onEscape, internalValue],
});
@@ -49,7 +49,6 @@ export type CurrencyInputProps = {
onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void;
onChange?: (newText: string) => void;
onSelect?: (newText: string) => void;
hotkeyScope: string;
};
export const CurrencyInput = ({
@@ -65,7 +64,6 @@ export const CurrencyInput = ({
onClickOutside,
onChange,
onSelect,
hotkeyScope,
}: CurrencyInputProps) => {
const theme = useTheme();
@@ -91,7 +89,6 @@ export const CurrencyInput = ({
onClickOutside,
onTab,
onShiftTab,
hotkeyScope,
});
const currency = CURRENCIES.find(({ value }) => value === currencyCode);
@@ -1,14 +1,13 @@
import { useRef, useState } from 'react';
import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import {
DateTimePicker,
MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID,
MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID,
} from '@/ui/input/components/internal/date/components/InternalDatePicker';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
import { currentFocusIdSelector } from '@/ui/utilities/focus/states/currentFocusIdSelector';
import { useRecoilCallback } from 'recoil';
import { Nullable } from 'twenty-ui/utilities';
@@ -27,7 +26,6 @@ export type DateInputProps = {
onClear?: () => void;
onSubmit?: (newDate: Nullable<Date>) => void;
hideHeaderInput?: boolean;
hotkeyScope: string;
};
export const DateInput = ({
@@ -42,7 +40,6 @@ export const DateInput = ({
onClear,
onSubmit,
hideHeaderInput,
hotkeyScope,
}: DateInputProps) => {
const [internalValue, setInternalValue] = useState(value);
@@ -83,17 +80,18 @@ export const DateInput = ({
const handleClickOutside = useRecoilCallback(
({ snapshot }) =>
(event: MouseEvent | TouchEvent) => {
const hotkeyScope = snapshot
.getLoadable(currentHotkeyScopeState)
const currentFocusId = snapshot
.getLoadable(currentFocusIdSelector)
.getValue();
if (hotkeyScope?.scope === TableHotkeyScope.CellEditMode) {
if (currentFocusId === instanceId) {
closeDropdownYearSelect(MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID);
closeDropdownMonthSelect(MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID);
onClickOutside(event, internalValue);
}
},
[
instanceId,
closeDropdownYearSelect,
closeDropdownMonthSelect,
onClickOutside,
@@ -108,7 +106,6 @@ export const DateInput = ({
onEnter: handleEnter,
onEscape: handleEscape,
onClickOutside: handleClickOutside,
hotkeyScope: hotkeyScope,
});
return (
@@ -34,7 +34,6 @@ type DoubleTextInputProps = {
secondValue: string;
firstValuePlaceholder: string;
secondValuePlaceholder: string;
hotkeyScope: string;
onEnter: (newDoubleTextValue: FieldDoubleText) => void;
onEscape: (newDoubleTextValue: FieldDoubleText) => void;
onTab?: (newDoubleTextValue: FieldDoubleText) => void;
@@ -53,7 +52,6 @@ export const DoubleTextInput = ({
secondValue,
firstValuePlaceholder,
secondValuePlaceholder,
hotkeyScope,
onClickOutside,
onEnter,
onEscape,
@@ -97,7 +95,6 @@ export const DoubleTextInput = ({
secondValue: secondInternalValue,
});
},
scope: hotkeyScope,
focusId: instanceId,
dependencies: [onEnter, firstInternalValue, secondInternalValue],
});
@@ -110,7 +107,6 @@ export const DoubleTextInput = ({
secondValue: secondInternalValue,
});
},
scope: hotkeyScope,
focusId: instanceId,
dependencies: [onEscape, firstInternalValue, secondInternalValue],
});
@@ -128,7 +124,6 @@ export const DoubleTextInput = ({
});
}
},
scope: hotkeyScope,
focusId: instanceId,
dependencies: [
onTab,
@@ -151,7 +146,6 @@ export const DoubleTextInput = ({
});
}
},
scope: hotkeyScope,
focusId: instanceId,
dependencies: [
onShiftTab,
@@ -2,7 +2,6 @@ import { useRef, useState } from 'react';
import { Key } from 'ts-key-enum';
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 { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
@@ -81,7 +80,6 @@ export const MultiSelectInput = ({
resetSelectedItem();
},
focusId,
scope: DEFAULT_CELL_SCOPE.scope,
dependencies: [onCancel, resetSelectedItem],
});
@@ -109,7 +107,6 @@ export const MultiSelectInput = ({
selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={optionIds}
focusId={focusId}
hotkeyScope={DEFAULT_CELL_SCOPE.scope}
>
<DropdownContent
ref={containerRef}
@@ -1,4 +1,3 @@
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { SelectInput as SelectBaseInput } from '@/ui/input/components/SelectInput';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectOption } from 'twenty-ui/input';
@@ -34,7 +33,6 @@ export const SelectInput = ({
selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={selectableItemIdArray}
focusId={focusId}
hotkeyScope={DEFAULT_CELL_SCOPE.scope}
>
<SelectBaseInput
onOptionSelected={onOptionSelected}
@@ -20,7 +20,6 @@ export type TextAreaInputProps = {
onTab?: (newText: string) => void;
onShiftTab?: (newText: string) => void;
onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void;
hotkeyScope: string;
onChange?: (newText: string) => void;
maxRows?: number;
copyButton?: boolean;
@@ -53,7 +52,6 @@ export const TextAreaInput = ({
placeholder,
autoFocus,
value,
hotkeyScope,
onEnter,
onEscape,
onTab,
@@ -94,7 +92,6 @@ export const TextAreaInput = ({
onClickOutside,
onTab,
onShiftTab,
hotkeyScope,
});
return (
@@ -25,7 +25,6 @@ type TextInputProps = {
onTab?: (newText: string) => void;
onShiftTab?: (newText: string) => void;
onClickOutside?: (event: MouseEvent | TouchEvent, inputValue: string) => void;
hotkeyScope: string;
onChange?: (newText: string) => void;
copyButton?: boolean;
shouldTrim?: boolean;
@@ -45,7 +44,6 @@ export const TextInput = ({
placeholder,
autoFocus,
value,
hotkeyScope,
onEnter,
onEscape,
onTab,
@@ -79,7 +77,6 @@ export const TextInput = ({
onClickOutside,
onTab,
onShiftTab,
hotkeyScope,
});
return (
@@ -1,6 +1,4 @@
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata';
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
@@ -80,6 +78,12 @@ const priorityOptions: SelectOption[] = [
{ value: 'urgent', label: 'Urgent', color: 'red' },
];
const instanceId = getRecordFieldInputInstanceId({
recordId: '123',
fieldName: 'Relation',
prefix: 'multi-select-story',
});
const Render = ({
values,
options,
@@ -94,23 +98,11 @@ const Render = ({
useEffect(() => {
pushFocusItemToFocusStack({
focusId: TableHotkeyScope.CellEditMode,
focusId: instanceId,
component: {
type: FocusComponentType.DROPDOWN,
instanceId: getRecordFieldInputInstanceId({
recordId: '123',
fieldName: 'Relation',
prefix: 'multi-select-story',
}),
instanceId,
},
hotkeyScope: {
scope: TableHotkeyScope.CellEditMode,
},
memoizeKey: getRecordFieldInputInstanceId({
recordId: '123',
fieldName: 'Relation',
prefix: 'multi-select-story',
}),
});
}, [pushFocusItemToFocusStack]);
@@ -125,7 +117,7 @@ const Render = ({
selectableListComponentInstanceId="multi-select-story"
values={currentValues}
options={options}
focusId={DEFAULT_CELL_SCOPE.scope}
focusId={instanceId}
onCancel={onCancel}
onOptionSelected={handleOptionSelected}
dropdownWidth={dropdownWidth}
@@ -10,7 +10,6 @@ import { arrayToChunks } from '~/utils/array/arrayToChunks';
import { ICON_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/ui/input/components/constants/IconPickerDropdownContentWidth';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@@ -223,7 +222,6 @@ export const IconPicker = ({
selectableListInstanceId={selectableListInstanceId}
selectableItemIdMatrix={iconKeys2d}
focusId={dropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
<DropdownMenuSearchInput
placeholder={t`Search icon`}
@@ -9,7 +9,6 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
import { SelectValue } from '@/ui/input/components/internal/select/types';
import { SelectControl } from '@/ui/input/components/SelectControl';
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 { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
@@ -186,7 +185,6 @@ export const Select = <Value extends SelectValue>({
selectableListInstanceId={dropdownId}
focusId={dropdownId}
selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
{filteredOptions.map((option) => (
<SelectableListItem
@@ -2,7 +2,6 @@ import styled from '@emotion/styled';
import { FocusEventHandler, useId } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { InputHotkeyScope } from '@/ui/input/types/InputHotkeyScope';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
@@ -101,7 +100,6 @@ export const TextArea = ({
globalHotkeysConfig: {
enableGlobalHotkeysConflictingWithKeyboard: false,
},
hotkeyScope: { scope: InputHotkeyScope.TextInput },
});
};
@@ -5,7 +5,6 @@ import {
TextInputV2,
TextInputV2ComponentProps,
} from '@/ui/input/components/TextInputV2';
import { InputHotkeyScope } from '@/ui/input/types/InputHotkeyScope';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
@@ -67,7 +66,6 @@ export const TextInput = ({
globalHotkeysConfig: {
enableGlobalHotkeysConflictingWithKeyboard: false,
},
hotkeyScope: { scope: InputHotkeyScope.TextInput },
});
}
};
@@ -105,7 +103,6 @@ export const TextInput = ({
keys: [Key.Escape],
callback: handleEscape,
focusId: instanceId,
scope: InputHotkeyScope.TextInput,
dependencies: [handleEscape],
options: {
preventDefault: false,
@@ -116,7 +113,6 @@ export const TextInput = ({
keys: [Key.Enter],
callback: handleEnter,
focusId: instanceId,
scope: InputHotkeyScope.TextInput,
dependencies: [handleEnter],
options: {
preventDefault: false,
@@ -17,7 +17,6 @@ type InputProps = {
value?: string;
onChange: (value: string) => void;
placeholder?: string;
hotkeyScope?: string;
onEnter?: () => void;
onEscape?: () => void;
onClickOutside?: () => void;
@@ -63,7 +62,6 @@ const Input = ({
value,
onChange,
placeholder,
hotkeyScope = 'title-input',
onEnter,
onEscape,
onClickOutside,
@@ -115,7 +113,6 @@ const Input = ({
handleLeaveFocus();
onShiftTab?.();
},
hotkeyScope: hotkeyScope,
});
return (
@@ -143,7 +140,6 @@ export const TitleInput = ({
sizeVariant = 'md',
onChange,
placeholder,
hotkeyScope = 'title-input',
onEnter,
onEscape,
onClickOutside,
@@ -163,7 +159,6 @@ export const TitleInput = ({
value={value}
onChange={onChange}
placeholder={placeholder}
hotkeyScope={hotkeyScope}
onEnter={onEnter}
onEscape={onEscape}
onClickOutside={onClickOutside}
@@ -187,7 +182,6 @@ export const TitleInput = ({
globalHotkeysConfig: {
enableGlobalHotkeysConflictingWithKeyboard: false,
},
hotkeyScope: { scope: hotkeyScope },
});
}
}}
@@ -9,11 +9,9 @@ const meta: Meta<typeof TitleInput> = {
decorators: [ComponentDecorator],
args: {
placeholder: 'Enter title',
hotkeyScope: 'titleInput',
sizeVariant: 'md',
},
argTypes: {
hotkeyScope: { control: false },
sizeVariant: { control: false },
},
};
@@ -4,8 +4,6 @@ import styled from '@emotion/styled';
import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { CurrencyPickerHotkeyScope } from '../types/CurrencyPickerHotkeyScope';
import { CURRENCIES } from '@/settings/data-model/constants/Currencies';
import { Currency } from '@/ui/input/components/internal/types/Currency';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
@@ -52,7 +50,7 @@ export const CurrencyPickerDropdownButton = ({
}) => {
const theme = useTheme();
const dropdownId = CurrencyPickerHotkeyScope.CurrencyPicker;
const dropdownId = 'currency-picker-dropdown-id';
const { closeDropdown } = useCloseDropdown();
@@ -69,7 +67,7 @@ export const CurrencyPickerDropdownButton = ({
return (
<Dropdown
dropdownId="currency-picker-dropdown-id"
dropdownId={dropdownId}
clickableComponent={
<StyledDropdownButtonContainer>
<StyledIconContainer>
@@ -1,3 +0,0 @@
export enum CurrencyPickerHotkeyScope {
CurrencyPicker = 'currency-picker-dropdown-id',
}
@@ -1,3 +0,0 @@
export enum CountryPickerHotkeyScope {
CountryPicker = 'country-picker',
}
@@ -1,3 +0,0 @@
export enum InputHotkeyScope {
TextInput = 'text-input',
}
@@ -1,3 +0,0 @@
export enum TitleInputHotkeyScope {
TitleInput = 'title-input',
}
@@ -53,7 +53,6 @@ type HTMLInputProps = InputHTMLAttributes<HTMLInputElement>;
export type DropdownMenuInputProps = HTMLInputProps & {
instanceId: string;
hotkeyScope?: string;
onClickOutside?: () => void;
onEnter?: () => void;
onEscape?: () => void;
@@ -81,7 +80,6 @@ export const DropdownMenuInput = forwardRef<
value,
placeholder,
instanceId,
hotkeyScope = 'dropdown-menu-input',
onChange,
onClickOutside,
onEnter = () => {},
@@ -107,7 +105,6 @@ export const DropdownMenuInput = forwardRef<
onClickOutside,
onTab,
onShiftTab,
hotkeyScope,
});
return (
@@ -9,12 +9,9 @@ import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { focusStackState } from '@/ui/utilities/focus/states/focusStackState';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
import { internalHotkeysEnabledScopesState } from '@/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState';
import { SetRecoilState } from 'recoil';
import { Avatar, IconChevronLeft } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
@@ -377,17 +374,6 @@ const initializeModalState = ({ set }: { set: SetRecoilState }) => {
true,
);
set(currentHotkeyScopeState, {
scope: ModalHotkeyScope.ModalFocus,
customScopes: {
commandMenu: true,
goto: false,
keyboardShortcutMenu: false,
},
});
set(internalHotkeysEnabledScopesState, [ModalHotkeyScope.ModalFocus]);
set(focusStackState, [
{
focusId: modalId,
@@ -399,7 +385,6 @@ const initializeModalState = ({ set }: { set: SetRecoilState }) => {
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true,
},
memoizeKey: 'global',
},
]);
};
@@ -1,13 +1,13 @@
import { Meta, StoryObj } from '@storybook/react';
import { DropdownMenuInput } from '../DropdownMenuInput';
import { ComponentDecorator } from 'twenty-ui/testing';
import { DropdownMenuInput } from '../DropdownMenuInput';
const meta: Meta<typeof DropdownMenuInput> = {
title: 'UI/Layout/Dropdown/DropdownMenuInput',
component: DropdownMenuInput,
decorators: [ComponentDecorator],
args: { value: 'Lorem ipsum' },
args: { value: 'Lorem ipsum', instanceId: 'dropdown-menu-input' },
};
export default meta;
@@ -1,5 +1,4 @@
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState';
@@ -127,7 +126,6 @@ export const DropdownInternalContainer = ({
}
},
focusId: dropdownId,
scope: DropdownHotkeyScope.Dropdown,
dependencies: [
closeDropdown,
isDropdownOpen,
@@ -1,3 +0,0 @@
export enum DropdownHotkeyScope {
Dropdown = 'dropdown',
}
@@ -5,7 +5,6 @@ import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDrop
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { GlobalHotkeysConfig } from '@/ui/utilities/hotkey/types/GlobalHotkeysConfig';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useAvailableComponentInstanceId } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceId';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -52,9 +51,6 @@ export const useOpenDropdown = () => {
instanceId: dropdownComponentInstanceId,
},
globalHotkeysConfig: args?.globalHotkeysConfig ?? undefined,
// TODO: Remove this once we've fully migrated away from hotkey scopes
hotkeyScope: { scope: 'dropdown' } as HotkeyScope,
memoizeKey: 'global',
});
},
[
@@ -1,6 +1,5 @@
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
import { ModalHotkeysAndClickOutsideEffect } from '@/ui/layout/modal/components/ModalHotkeysAndClickOutsideEffect';
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
import { ModalComponentInstanceContext } from '@/ui/layout/modal/contexts/ModalComponentInstanceContext';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
@@ -191,7 +190,6 @@ export type ModalProps = React.PropsWithChildren & {
size?: ModalSize;
padding?: ModalPadding;
className?: string;
hotkeyScope?: ModalHotkeyScope;
onEnter?: () => void;
modalVariant?: ModalVariants;
dataGloballyPreventClickOutside?: boolean;
@@ -1,5 +1,4 @@
import { DIALOG_CLICK_OUTSIDE_ID } from '@/ui/feedback/dialog-manager/constants/DialogClickOutsideId';
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
import { MODAL_CLICK_OUTSIDE_LISTENER_EXCLUDED_ID } from '@/ui/layout/modal/constants/ModalClickOutsideListenerExcludedClassName';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
@@ -26,8 +25,6 @@ export const ModalHotkeysAndClickOutsideEffect = ({
onEnter?.();
},
focusId: modalId,
// TODO: Remove this once we've migrated hotkey scopes to the new api
scope: ModalHotkeyScope.ModalFocus,
dependencies: [onEnter],
});
@@ -39,8 +36,6 @@ export const ModalHotkeysAndClickOutsideEffect = ({
}
},
focusId: modalId,
// TODO: Remove this once we've migrated hotkey scopes to the new api
scope: ModalHotkeyScope.ModalFocus,
dependencies: [isClosable, onClose],
});
@@ -1,11 +1,8 @@
import { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
import { focusStackState } from '@/ui/utilities/focus/states/focusStackState';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
import { internalHotkeysEnabledScopesState } from '@/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState';
import { SetRecoilState } from 'recoil';
import { ComponentDecorator } from 'twenty-ui/testing';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
@@ -22,17 +19,6 @@ const initializeState = ({ set }: { set: SetRecoilState }) => {
true,
);
set(currentHotkeyScopeState, {
scope: ModalHotkeyScope.ModalFocus,
customScopes: {
commandMenu: true,
goto: false,
keyboardShortcutMenu: false,
},
});
set(internalHotkeysEnabledScopesState, [ModalHotkeyScope.ModalFocus]);
set(focusStackState, [
{
focusId: 'confirmation-modal',
@@ -44,7 +30,6 @@ const initializeState = ({ set }: { set: SetRecoilState }) => {
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true,
},
memoizeKey: 'global',
},
]);
};
@@ -1,11 +1,8 @@
import { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
import { focusStackState } from '@/ui/utilities/focus/states/focusStackState';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
import { internalHotkeysEnabledScopesState } from '@/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState';
import { SetRecoilState } from 'recoil';
import { ComponentDecorator } from 'twenty-ui/testing';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
@@ -22,17 +19,6 @@ const initializeState = ({ set }: { set: SetRecoilState }) => {
true,
);
set(currentHotkeyScopeState, {
scope: ModalHotkeyScope.ModalFocus,
customScopes: {
commandMenu: true,
goto: false,
keyboardShortcutMenu: false,
},
});
set(internalHotkeysEnabledScopesState, [ModalHotkeyScope.ModalFocus]);
set(focusStackState, [
{
focusId: 'modal-id',
@@ -44,7 +30,6 @@ const initializeState = ({ set }: { set: SetRecoilState }) => {
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true,
},
memoizeKey: 'global',
},
]);
};
@@ -1,3 +0,0 @@
export enum ModalHotkeyScope {
ModalFocus = 'modal-focus',
}
@@ -3,24 +3,13 @@ import { RecoilRoot, useRecoilValue } from 'recoil';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { act } from 'react';
jest.mock('@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope');
const mockSetHotkeyScopeAndMemorizePreviousScope = jest.fn();
const mockGoBackToPreviousHotkeyScope = jest.fn();
const modalId = 'test-modal-id';
describe('useModal', () => {
beforeEach(() => {
jest.clearAllMocks();
(usePreviousHotkeyScope as jest.Mock).mockReturnValue({
setHotkeyScopeAndMemorizePreviousScope:
mockSetHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope: mockGoBackToPreviousHotkeyScope,
});
});
it('should open a modal', () => {
@@ -68,7 +57,6 @@ describe('useModal', () => {
});
expect(result.current.isModalOpened).toBe(false);
expect(mockGoBackToPreviousHotkeyScope).toHaveBeenCalled();
});
it('should toggle a modal (open when closed)', () => {
@@ -118,6 +106,5 @@ describe('useModal', () => {
});
expect(result.current.isModalOpened).toBe(false);
expect(mockGoBackToPreviousHotkeyScope).toHaveBeenCalled();
});
});
@@ -1,4 +1,3 @@
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
@@ -63,17 +62,6 @@ export const useModal = () => {
enableGlobalHotkeysWithModifiers: false,
enableGlobalHotkeysConflictingWithKeyboard: false,
},
// TODO: Remove this once we've migrated hotkey scopes to the new api
hotkeyScope: {
scope: ModalHotkeyScope.ModalFocus,
customScopes: {
goto: false,
commandMenu: false,
commandMenuOpen: false,
keyboardShortcutMenu: false,
},
},
memoizeKey: modalId,
});
},
[pushFocusItemToFocusStack],
@@ -15,7 +15,6 @@ type SelectableListProps = {
onSelect?: (selected: string) => void;
selectableListInstanceId: string;
focusId: string;
hotkeyScope: string;
};
export const SelectableList = ({
@@ -25,14 +24,8 @@ export const SelectableList = ({
selectableListInstanceId,
onSelect,
focusId,
hotkeyScope,
}: SelectableListProps) => {
useSelectableListHotKeys(
selectableListInstanceId,
hotkeyScope,
focusId,
onSelect,
);
useSelectableListHotKeys(selectableListInstanceId, focusId, onSelect);
const setSelectableItemIds = useSetRecoilComponentStateV2(
selectableItemIdsComponentState,
@@ -61,7 +54,7 @@ export const SelectableList = ({
instanceId: selectableListInstanceId,
}}
>
<SelectableListContextProvider value={{ focusId, hotkeyScope }}>
<SelectableListContextProvider value={{ focusId }}>
{children}
</SelectableListContextProvider>
</SelectableListComponentInstanceContext.Provider>
@@ -8,13 +8,12 @@ export const SelectableListItemHotkeyEffect = ({
itemId: string;
onEnter: () => void;
}) => {
const { focusId, hotkeyScope } = useSelectableListContextOrThrow();
const { focusId } = useSelectableListContextOrThrow();
useSelectableListListenToEnterHotkeyOnItem({
focusId,
itemId,
onEnter,
hotkeyScope,
});
return null;
@@ -12,8 +12,6 @@ type Direction = 'up' | 'down' | 'left' | 'right';
export const useSelectableListHotKeys = (
instanceId: string,
// TODO: Remove this after migration to focus stack
hotkeyScope: string,
focusId: string,
onSelect?: (itemId: string) => void,
) => {
@@ -140,7 +138,6 @@ export const useSelectableListHotKeys = (
keys: Key.ArrowUp,
callback: () => handleSelect('up'),
focusId,
scope: hotkeyScope,
dependencies: [handleSelect],
});
@@ -148,7 +145,6 @@ export const useSelectableListHotKeys = (
keys: Key.ArrowDown,
callback: () => handleSelect('down'),
focusId,
scope: hotkeyScope,
dependencies: [handleSelect],
});
@@ -156,7 +152,6 @@ export const useSelectableListHotKeys = (
keys: Key.ArrowLeft,
callback: () => handleSelect('left'),
focusId,
scope: hotkeyScope,
dependencies: [handleSelect],
});
@@ -164,7 +159,6 @@ export const useSelectableListHotKeys = (
keys: Key.ArrowRight,
callback: () => handleSelect('right'),
focusId,
scope: hotkeyScope,
dependencies: [handleSelect],
});
};
@@ -11,13 +11,10 @@ export const useSelectableListListenToEnterHotkeyOnItem = ({
focusId,
itemId,
onEnter,
hotkeyScope,
}: {
focusId: string;
itemId: string;
onEnter: () => void;
// TODO: Remove this after migration to focus stack
hotkeyScope: string;
}) => {
const instanceId = useAvailableComponentInstanceIdOrThrow(
SelectableListComponentInstanceContext,
@@ -42,7 +39,6 @@ export const useSelectableListListenToEnterHotkeyOnItem = ({
[instanceId, itemId, onEnter],
),
focusId,
scope: hotkeyScope,
dependencies: [itemId, onEnter],
});
};
@@ -2,7 +2,6 @@ import { createRequiredContext } from '~/utils/createRequiredContext';
export type SelectableListContextValue = {
focusId: string;
hotkeyScope: string;
};
export const [SelectableListContextProvider, useSelectableListContextOrThrow] =
@@ -18,7 +18,6 @@ type NavigationDrawerInputProps = {
onSubmit: (value: string) => void;
onCancel: (value: string) => void;
onClickOutside: (event: MouseEvent | TouchEvent, value: string) => void;
hotkeyScope: string;
};
const NAVIGATION_DRAWER_INPUT_FOCUS_ID = 'navigation-drawer-input';
@@ -32,7 +31,6 @@ export const NavigationDrawerInput = ({
onSubmit,
onCancel,
onClickOutside,
hotkeyScope,
}: NavigationDrawerInputProps) => {
const inputRef = useRef<HTMLInputElement>(null);
@@ -45,7 +43,6 @@ export const NavigationDrawerInput = ({
});
},
focusId: NAVIGATION_DRAWER_INPUT_FOCUS_ID,
scope: hotkeyScope,
});
useHotkeysOnFocusedElement({
@@ -57,7 +54,6 @@ export const NavigationDrawerInput = ({
});
},
focusId: NAVIGATION_DRAWER_INPUT_FOCUS_ID,
scope: hotkeyScope,
});
useListenClickOutside({
@@ -86,7 +82,9 @@ export const NavigationDrawerInput = ({
type: FocusComponentType.TEXT_INPUT,
instanceId: NAVIGATION_DRAWER_INPUT_FOCUS_ID,
},
hotkeyScope: { scope: hotkeyScope },
globalHotkeysConfig: {
enableGlobalHotkeysConflictingWithKeyboard: false,
},
});
};
@@ -1,3 +0,0 @@
export enum NavigationDrawerHotKeyScope {
MultiWorkspaceDropdownButton = 'multi-workspace-dropdown',
}
@@ -1,3 +1 @@
import { DEBUG_HOTKEY_SCOPE } from '@/ui/utilities/hotkey/constants/DebugHotkeyScope';
export const DEBUG_FOCUS_STACK = DEBUG_HOTKEY_SCOPE;
export const DEBUG_FOCUS_STACK = false;
@@ -43,7 +43,6 @@ describe('usePushFocusItemToFocusStack', () => {
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true,
},
memoizeKey: 'global',
};
await act(async () => {
@@ -53,8 +52,6 @@ describe('usePushFocusItemToFocusStack', () => {
type: focusItem.componentInstance.componentType,
instanceId: focusItem.componentInstance.componentInstanceId,
},
hotkeyScope: { scope: 'test-scope' },
memoizeKey: 'global',
});
});
@@ -71,7 +68,6 @@ describe('usePushFocusItemToFocusStack', () => {
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true,
},
memoizeKey: 'global',
};
await act(async () => {
@@ -81,8 +77,6 @@ describe('usePushFocusItemToFocusStack', () => {
type: anotherFocusItem.componentInstance.componentType,
instanceId: anotherFocusItem.componentInstance.componentInstanceId,
},
hotkeyScope: { scope: 'test-scope' },
memoizeKey: 'global',
});
});
@@ -41,7 +41,6 @@ const firstFocusItem = {
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true,
},
memoizeKey: 'global',
};
const secondFocusItem = {
@@ -54,7 +53,6 @@ const secondFocusItem = {
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true,
},
memoizeKey: 'global',
};
describe('useRemoveFocusItemFromFocusStackById', () => {
@@ -68,8 +66,6 @@ describe('useRemoveFocusItemFromFocusStackById', () => {
type: firstFocusItem.componentInstance.componentType,
instanceId: firstFocusItem.componentInstance.componentInstanceId,
},
hotkeyScope: { scope: 'test-scope' },
memoizeKey: firstFocusItem.memoizeKey,
});
});
@@ -80,8 +76,6 @@ describe('useRemoveFocusItemFromFocusStackById', () => {
type: secondFocusItem.componentInstance.componentType,
instanceId: secondFocusItem.componentInstance.componentInstanceId,
},
hotkeyScope: { scope: 'test-scope' },
memoizeKey: secondFocusItem.memoizeKey,
});
});
@@ -111,8 +105,6 @@ describe('useRemoveFocusItemFromFocusStackById', () => {
type: firstFocusItem.componentInstance.componentType,
instanceId: firstFocusItem.componentInstance.componentInstanceId,
},
hotkeyScope: { scope: 'test-scope' },
memoizeKey: 'global',
});
});
@@ -123,8 +115,6 @@ describe('useRemoveFocusItemFromFocusStackById', () => {
type: secondFocusItem.componentInstance.componentType,
instanceId: secondFocusItem.componentInstance.componentInstanceId,
},
hotkeyScope: { scope: 'test-scope' },
memoizeKey: 'global',
});
});
@@ -44,7 +44,6 @@ describe('useResetFocusStack', () => {
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true,
},
memoizeKey: 'global',
};
await act(async () => {
@@ -54,8 +53,6 @@ describe('useResetFocusStack', () => {
type: focusItem.componentInstance.componentType,
instanceId: focusItem.componentInstance.componentInstanceId,
},
hotkeyScope: { scope: 'test-scope' },
memoizeKey: 'global',
});
});
@@ -44,7 +44,6 @@ describe('useResetFocusStackToFocusItem', () => {
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true,
},
memoizeKey: 'global',
};
const secondFocusItem = {
@@ -57,7 +56,6 @@ describe('useResetFocusStackToFocusItem', () => {
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: true,
},
memoizeKey: 'global',
};
await act(async () => {
@@ -67,8 +65,6 @@ describe('useResetFocusStackToFocusItem', () => {
type: firstFocusItem.componentInstance.componentType,
instanceId: firstFocusItem.componentInstance.componentInstanceId,
},
hotkeyScope: { scope: 'test-scope' },
memoizeKey: firstFocusItem.memoizeKey,
});
});
@@ -79,8 +75,6 @@ describe('useResetFocusStackToFocusItem', () => {
type: secondFocusItem.componentInstance.componentType,
instanceId: secondFocusItem.componentInstance.componentInstanceId,
},
hotkeyScope: { scope: 'test-scope' },
memoizeKey: secondFocusItem.memoizeKey,
});
});
@@ -93,8 +87,6 @@ describe('useResetFocusStackToFocusItem', () => {
await act(async () => {
result.current.resetFocusStackToFocusItem({
focusStackItem: firstFocusItem,
hotkeyScope: { scope: 'test-scope' },
memoizeKey: firstFocusItem.memoizeKey,
});
});
@@ -2,9 +2,7 @@ import { DEBUG_FOCUS_STACK } from '@/ui/utilities/focus/constants/DebugFocusStac
import { focusStackState } from '@/ui/utilities/focus/states/focusStackState';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { FocusStackItem } from '@/ui/utilities/focus/types/FocusStackItem';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { GlobalHotkeysConfig } from '@/ui/utilities/hotkey/types/GlobalHotkeysConfig';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilCallback } from 'recoil';
import { logDebug } from '~/utils/logDebug';
@@ -23,15 +21,11 @@ const addOrMoveItemToTheTopOfTheStack = ({
];
export const usePushFocusItemToFocusStack = () => {
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
const pushFocusItemToFocusStack = useRecoilCallback(
({ snapshot, set }) =>
({
focusId,
component,
hotkeyScope,
memoizeKey = 'global',
globalHotkeysConfig,
}: {
focusId: string;
@@ -40,9 +34,6 @@ export const usePushFocusItemToFocusStack = () => {
instanceId: string;
};
globalHotkeysConfig?: Partial<GlobalHotkeysConfig>;
// TODO: Remove this once we've migrated hotkey scopes to the new api
hotkeyScope: HotkeyScope;
memoizeKey?: string;
}) => {
const focusStackItem: FocusStackItem = {
focusId,
@@ -57,8 +48,6 @@ export const usePushFocusItemToFocusStack = () => {
globalHotkeysConfig?.enableGlobalHotkeysConflictingWithKeyboard ??
true,
},
// TODO: Remove this once we've migrated hotkey scopes to the new api
memoizeKey,
};
const currentFocusStack = snapshot
@@ -78,15 +67,8 @@ export const usePushFocusItemToFocusStack = () => {
newFocusStack,
});
}
// TODO: Remove this once we've migrated hotkey scopes to the new api
setHotkeyScopeAndMemorizePreviousScope({
scope: hotkeyScope.scope,
customScopes: hotkeyScope.customScopes,
memoizeKey,
});
},
[setHotkeyScopeAndMemorizePreviousScope],
[],
);
return { pushFocusItemToFocusStack };
@@ -1,13 +1,10 @@
import { DEBUG_FOCUS_STACK } from '@/ui/utilities/focus/constants/DebugFocusStack';
import { focusStackState } from '@/ui/utilities/focus/states/focusStackState';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useRecoilCallback } from 'recoil';
import { logDebug } from '~/utils/logDebug';
export const useRemoveLastFocusItemFromFocusStackByComponentType = () => {
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
const removeLastFocusItemFromFocusStackByComponentType = useRecoilCallback(
({ snapshot, set }) =>
({ componentType }: { componentType: FocusComponentType }) => {
@@ -44,11 +41,8 @@ export const useRemoveLastFocusItemFromFocusStackByComponentType = () => {
},
);
}
// TODO: Remove this once we've migrated hotkey scopes to the new api
goBackToPreviousHotkeyScope(removedFocusItem.memoizeKey);
},
[goBackToPreviousHotkeyScope],
[],
);
return { removeLastFocusItemFromFocusStackByComponentType };
@@ -1,12 +1,9 @@
import { DEBUG_FOCUS_STACK } from '@/ui/utilities/focus/constants/DebugFocusStack';
import { focusStackState } from '@/ui/utilities/focus/states/focusStackState';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useRecoilCallback } from 'recoil';
import { logDebug } from '~/utils/logDebug';
export const useRemoveFocusItemFromFocusStackById = () => {
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
const removeFocusItemFromFocusStackById = useRecoilCallback(
({ snapshot, set }) =>
({ focusId }: { focusId: string }) => {
@@ -31,11 +28,8 @@ export const useRemoveFocusItemFromFocusStackById = () => {
newFocusStack,
});
}
// TODO: Remove this once we've migrated hotkey scopes to the new api
goBackToPreviousHotkeyScope(removedFocusItem.memoizeKey);
},
[goBackToPreviousHotkeyScope],
[],
);
return { removeFocusItemFromFocusStackById };
@@ -1,23 +1,17 @@
import { DEBUG_FOCUS_STACK } from '@/ui/utilities/focus/constants/DebugFocusStack';
import { focusStackState } from '@/ui/utilities/focus/states/focusStackState';
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
import { previousHotkeyScopeFamilyState } from '@/ui/utilities/hotkey/states/internal/previousHotkeyScopeFamilyState';
import { useRecoilCallback } from 'recoil';
import { logDebug } from '~/utils/logDebug';
export const useResetFocusStack = () => {
const resetFocusStack = useRecoilCallback(
({ reset }) =>
(memoizeKey = 'global') => {
() => {
reset(focusStackState);
if (DEBUG_FOCUS_STACK) {
logDebug(`DEBUG: reset focus stack`);
}
// TODO: Remove this once we've migrated hotkey scopes to the new api
reset(previousHotkeyScopeFamilyState(memoizeKey as string));
reset(currentHotkeyScopeState);
},
[],
);
@@ -1,26 +1,13 @@
import { DEBUG_FOCUS_STACK } from '@/ui/utilities/focus/constants/DebugFocusStack';
import { focusStackState } from '@/ui/utilities/focus/states/focusStackState';
import { FocusStackItem } from '@/ui/utilities/focus/types/FocusStackItem';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { previousHotkeyScopeFamilyState } from '@/ui/utilities/hotkey/states/internal/previousHotkeyScopeFamilyState';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilCallback } from 'recoil';
import { logDebug } from '~/utils/logDebug';
export const useResetFocusStackToFocusItem = () => {
const setHotkeyScope = useSetHotkeyScope();
const resetFocusStackToFocusItem = useRecoilCallback(
({ set }) =>
({
focusStackItem,
hotkeyScope,
memoizeKey = 'global',
}: {
focusStackItem: FocusStackItem;
hotkeyScope: HotkeyScope;
memoizeKey?: string;
}) => {
({ focusStackItem }: { focusStackItem: FocusStackItem }) => {
set(focusStackState, [focusStackItem]);
if (DEBUG_FOCUS_STACK) {
@@ -28,12 +15,8 @@ export const useResetFocusStackToFocusItem = () => {
focusStackItem,
});
}
// TODO: Remove this once we've migrated hotkey scopes to the new api
set(previousHotkeyScopeFamilyState(memoizeKey), null);
setHotkeyScope(hotkeyScope.scope, hotkeyScope.customScopes);
},
[setHotkeyScope],
[],
);
return { resetFocusStackToFocusItem };
@@ -5,5 +5,4 @@ export type FocusStackItem = {
focusId: string;
componentInstance: FocusComponentInstance;
globalHotkeysConfig: GlobalHotkeysConfig;
memoizeKey: string;
};
@@ -1,6 +1,5 @@
import { Keys } from 'react-hotkeys-hook';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
type HotkeyEffectProps = {
@@ -20,7 +19,6 @@ export const HotkeyEffect = ({
keys: hotkey.key,
callback: onHotkeyTriggered,
focusId,
scope: DropdownHotkeyScope.Dropdown,
dependencies: [onHotkeyTriggered],
});
@@ -1 +0,0 @@
export const DEBUG_HOTKEY_SCOPE = false;
@@ -1,7 +0,0 @@
import { CustomHotkeyScopes } from '@/ui/utilities/hotkey/types/CustomHotkeyScope';
export const DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES: CustomHotkeyScopes = {
commandMenu: true,
goto: false,
keyboardShortcutMenu: false,
};
@@ -1,11 +0,0 @@
import { AppHotkeyScope } from '../types/AppHotkeyScope';
import { HotkeyScope } from '../types/HotkeyScope';
export const INITIAL_HOTKEYS_SCOPE: HotkeyScope = {
scope: AppHotkeyScope.App,
customScopes: {
commandMenu: true,
goto: true,
keyboardShortcutMenu: true,
},
};
@@ -2,9 +2,6 @@ import { act, fireEvent, renderHook } from '@testing-library/react';
import { MemoryRouter, useLocation } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useGoToHotkeys } from '../useGoToHotkeys';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
@@ -24,10 +21,6 @@ describe('useGoToHotkeys', () => {
const { result } = renderHook(() => {
useGoToHotkeys({ key: 'a', location: '/three' });
const setHotkeyScope = useSetHotkeyScope();
setHotkeyScope(AppHotkeyScope.App, { goto: true });
const location = useLocation();
return {
@@ -11,8 +11,6 @@ export const useGlobalHotkeys = (
keys: Keys,
callback: HotkeyCallback,
containsModifier: boolean,
// TODO: Remove this once we've migrated hotkey scopes to the new api
scope: string,
dependencies?: unknown[],
options?: UseHotkeysOptionsWithoutBuggyOptions,
) => {
@@ -59,7 +57,6 @@ export const useGlobalHotkeys = (
callback: () => {
handleCallback(keyboardEvent, hotkeysEvent);
},
scope,
preventDefault,
containsModifier,
});
@@ -1,12 +1,11 @@
import { DEBUG_FOCUS_STACK } from '@/ui/utilities/focus/constants/DebugFocusStack';
import { currentGlobalHotkeysConfigSelector } from '@/ui/utilities/focus/states/currentGlobalHotkeysConfigSelector';
import { internalHotkeysEnabledScopesState } from '@/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState';
import {
Hotkey,
OptionsOrDependencyArray,
} from 'react-hotkeys-hook/dist/types';
import { useRecoilCallback } from 'recoil';
import { logDebug } from '~/utils/logDebug';
import { DEBUG_HOTKEY_SCOPE } from '../constants/DebugHotkeyScope';
export const useGlobalHotkeysCallback = (
dependencies?: OptionsOrDependencyArray,
@@ -21,20 +20,13 @@ export const useGlobalHotkeysCallback = (
hotkeysEvent,
keyboardEvent,
preventDefault,
scope,
}: {
keyboardEvent: KeyboardEvent;
hotkeysEvent: Hotkey;
containsModifier: boolean;
callback: (keyboardEvent: KeyboardEvent, hotkeysEvent: Hotkey) => void;
preventDefault?: boolean;
scope: string;
}) => {
// TODO: Remove this once we've migrated hotkey scopes to the new api
const currentHotkeyScopes = snapshot
.getLoadable(internalHotkeysEnabledScopesState)
.getValue();
const currentGlobalHotkeysConfig = snapshot
.getLoadable(currentGlobalHotkeysConfigSelector)
.getValue();
@@ -43,7 +35,7 @@ export const useGlobalHotkeysCallback = (
containsModifier &&
!currentGlobalHotkeysConfig.enableGlobalHotkeysWithModifiers
) {
if (DEBUG_HOTKEY_SCOPE) {
if (DEBUG_FOCUS_STACK) {
logDebug(
`DEBUG: %cI can't call hotkey (${
hotkeysEvent.keys
@@ -59,7 +51,7 @@ export const useGlobalHotkeysCallback = (
!containsModifier &&
!currentGlobalHotkeysConfig.enableGlobalHotkeysConflictingWithKeyboard
) {
if (DEBUG_HOTKEY_SCOPE) {
if (DEBUG_FOCUS_STACK) {
logDebug(
`DEBUG: %cI can't call hotkey (${
hotkeysEvent.keys
@@ -70,36 +62,8 @@ export const useGlobalHotkeysCallback = (
return;
}
// TODO: Remove this once we've migrated hotkey scopes to the new api
if (!currentHotkeyScopes.includes(scope)) {
if (DEBUG_HOTKEY_SCOPE) {
logDebug(
`DEBUG: %cI can't call hotkey (${
hotkeysEvent.keys
}) because I'm in scope [${scope}] and the active scopes are : [${currentHotkeyScopes.join(
', ',
)}]`,
'color: gray; ',
);
}
return;
}
// TODO: Remove this once we've migrated hotkey scopes to the new api
if (DEBUG_HOTKEY_SCOPE) {
logDebug(
`DEBUG: %cI can call hotkey (${
hotkeysEvent.keys
}) because I'm in scope [${scope}] and the active scopes are : [${currentHotkeyScopes.join(
', ',
)}]`,
'color: green;',
);
}
if (preventDefault === true) {
if (DEBUG_HOTKEY_SCOPE) {
if (DEBUG_FOCUS_STACK) {
logDebug(
`DEBUG: %cI prevent default for hotkey (${hotkeysEvent.keys})`,
'color: gray;',
@@ -11,7 +11,6 @@ export const useGlobalHotkeysSequence = (
firstKey: Keys,
secondKey: Keys,
sequenceCallback: () => void,
scope: string,
options: Options = {
enableOnContentEditable: true,
enableOnFormTags: true,
@@ -33,7 +32,6 @@ export const useGlobalHotkeysSequence = (
callback: () => {
setPendingHotkey(firstKey);
},
scope,
preventDefault: !!options.preventDefault,
});
},
@@ -41,7 +39,7 @@ export const useGlobalHotkeysSequence = (
enableOnContentEditable: options.enableOnContentEditable,
enableOnFormTags: options.enableOnFormTags,
},
[setPendingHotkey, scope],
[setPendingHotkey],
);
useHotkeys(
@@ -66,7 +64,6 @@ export const useGlobalHotkeysSequence = (
sequenceCallback();
},
scope,
preventDefault: false,
});
},
@@ -74,6 +71,6 @@ export const useGlobalHotkeysSequence = (
enableOnContentEditable: options.enableOnContentEditable,
enableOnFormTags: options.enableOnFormTags,
},
[pendingHotkey, setPendingHotkey, scope, ...deps],
[pendingHotkey, setPendingHotkey, ...deps],
);
};
@@ -2,7 +2,6 @@ import { Keys } from 'react-hotkeys-hook/dist/types';
import { useNavigate } from 'react-router-dom';
import { useGlobalHotkeysSequence } from '@/ui/utilities/hotkey/hooks/useGlobalHotkeysSequence';
import { AppHotkeyScope } from '../types/AppHotkeyScope';
type GoToHotkeysProps = {
key: Keys;
@@ -24,7 +23,6 @@ export const useGoToHotkeys = ({
preNavigateFunction?.();
navigate(location);
},
AppHotkeyScope.Goto,
{
enableOnContentEditable: true,
enableOnFormTags: true,
@@ -11,16 +11,12 @@ export const useHotkeysOnFocusedElement = ({
keys,
callback,
focusId,
// TODO: Remove this once we've migrated hotkey scopes to the new api
scope,
dependencies,
options,
}: {
keys: Keys;
callback: HotkeyCallback;
focusId: string;
// TODO: Remove this once we've migrated hotkey scopes to the new api
scope: string;
dependencies?: unknown[];
options?: UseHotkeysOptionsWithoutBuggyOptions;
}) => {
@@ -59,7 +55,6 @@ export const useHotkeysOnFocusedElement = ({
setPendingHotkey(null);
},
focusId,
scope,
preventDefault,
});
},
@@ -1,4 +1,4 @@
import { internalHotkeysEnabledScopesState } from '@/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState';
import { DEBUG_FOCUS_STACK } from '@/ui/utilities/focus/constants/DebugFocusStack';
import {
Hotkey,
OptionsOrDependencyArray,
@@ -6,7 +6,6 @@ import {
import { useRecoilCallback } from 'recoil';
import { logDebug } from '~/utils/logDebug';
import { currentFocusIdSelector } from '../../focus/states/currentFocusIdSelector';
import { DEBUG_HOTKEY_SCOPE } from '../constants/DebugHotkeyScope';
export const useHotkeysOnFocusedElementCallback = (
dependencies?: OptionsOrDependencyArray,
@@ -20,36 +19,24 @@ export const useHotkeysOnFocusedElementCallback = (
hotkeysEvent,
keyboardEvent,
focusId,
scope,
preventDefault,
}: {
keyboardEvent: KeyboardEvent;
hotkeysEvent: Hotkey;
callback: (keyboardEvent: KeyboardEvent, hotkeysEvent: Hotkey) => void;
focusId: string;
scope: string;
preventDefault?: boolean;
}) => {
const currentFocusId = snapshot
.getLoadable(currentFocusIdSelector)
.getValue();
// TODO: Remove this once we've migrated hotkey scopes to the new api
const currentHotkeyScopes = snapshot
.getLoadable(internalHotkeysEnabledScopesState)
.getValue();
if (
currentFocusId !== focusId ||
!currentHotkeyScopes.includes(scope)
) {
if (DEBUG_HOTKEY_SCOPE) {
if (currentFocusId !== focusId) {
if (DEBUG_FOCUS_STACK) {
logDebug(
`DEBUG: %cI can't call hotkey (${
hotkeysEvent.keys
}) because I'm in scope [${scope}] and the active scopes are : [${currentHotkeyScopes.join(
', ',
)}] and the current focus identifier is [${currentFocusId}], and the focusId is [${focusId}]`,
}) because I'm in [${focusId}] and the current focus identifier is [${currentFocusId}]`,
'color: gray; ',
);
}
@@ -57,19 +44,17 @@ export const useHotkeysOnFocusedElementCallback = (
return;
}
if (DEBUG_HOTKEY_SCOPE) {
if (DEBUG_FOCUS_STACK) {
logDebug(
`DEBUG: %cI can call hotkey (${
hotkeysEvent.keys
}) because I'm in scope [${scope}] and the active scopes are : [${currentHotkeyScopes.join(
', ',
)}], and the current focus identifier is [${currentFocusId}], and the focusId is [${focusId}]`,
}) because I'm in [${focusId}] and the current focus identifier is [${currentFocusId}]`,
'color: green;',
);
}
if (preventDefault === true) {
if (DEBUG_HOTKEY_SCOPE) {
if (DEBUG_FOCUS_STACK) {
logDebug(
`DEBUG: %cI prevent default for hotkey (${hotkeysEvent.keys})`,
'color: gray;',
@@ -1,81 +0,0 @@
import { useRecoilCallback } from 'recoil';
import { logDebug } from '~/utils/logDebug';
import { DEBUG_HOTKEY_SCOPE } from '../constants/DebugHotkeyScope';
import { currentHotkeyScopeState } from '../states/internal/currentHotkeyScopeState';
import { previousHotkeyScopeFamilyState } from '../states/internal/previousHotkeyScopeFamilyState';
import { CustomHotkeyScopes } from '../types/CustomHotkeyScope';
import { useSetHotkeyScope } from './useSetHotkeyScope';
export const usePreviousHotkeyScope = () => {
const setHotkeyScope = useSetHotkeyScope();
const goBackToPreviousHotkeyScope = useRecoilCallback(
({ snapshot, set }) =>
(memoizeKey = 'global') => {
const previousHotkeyScope = snapshot
.getLoadable(previousHotkeyScopeFamilyState(memoizeKey as string))
.getValue();
if (!previousHotkeyScope) {
if (DEBUG_HOTKEY_SCOPE) {
logDebug(`DEBUG: no previous hotkey scope ${memoizeKey}`);
}
return;
}
if (DEBUG_HOTKEY_SCOPE) {
logDebug(
`DEBUG: goBackToPreviousHotkeyScope ${previousHotkeyScope.scope}`,
previousHotkeyScope,
);
}
setHotkeyScope(
previousHotkeyScope.scope,
previousHotkeyScope.customScopes,
);
set(previousHotkeyScopeFamilyState(memoizeKey as string), null);
},
[setHotkeyScope],
);
const setHotkeyScopeAndMemorizePreviousScope = useRecoilCallback(
({ snapshot, set }) =>
({
scope,
customScopes,
memoizeKey = 'global',
}: {
scope: string;
customScopes?: CustomHotkeyScopes;
memoizeKey?: string;
}) => {
const currentHotkeyScope = snapshot
.getLoadable(currentHotkeyScopeState)
.getValue();
if (DEBUG_HOTKEY_SCOPE) {
logDebug('DEBUG: setHotkeyScopeAndMemorizePreviousScope', {
currentHotkeyScope,
scope,
customScopes,
});
}
setHotkeyScope(scope, customScopes);
set(previousHotkeyScopeFamilyState(memoizeKey), currentHotkeyScope);
},
[setHotkeyScope],
);
return {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
};
};
@@ -1,104 +0,0 @@
import { useRecoilCallback } from 'recoil';
import { DEBUG_HOTKEY_SCOPE } from '../constants/DebugHotkeyScope';
import { isDefined } from 'twenty-shared/utils';
import { logDebug } from '~/utils/logDebug';
import { DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES } from '../constants/DefaultHotkeysScopeCustomScopes';
import { currentHotkeyScopeState } from '../states/internal/currentHotkeyScopeState';
import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState';
import { AppHotkeyScope } from '../types/AppHotkeyScope';
import { CustomHotkeyScopes } from '../types/CustomHotkeyScope';
import { HotkeyScope } from '../types/HotkeyScope';
const areCustomScopesEqual = (
customScopesA: CustomHotkeyScopes | undefined,
customScopesB: CustomHotkeyScopes | undefined,
) => {
return (
customScopesA?.commandMenu === customScopesB?.commandMenu &&
customScopesA?.commandMenuOpen === customScopesB?.commandMenuOpen &&
customScopesA?.goto === customScopesB?.goto &&
customScopesA?.keyboardShortcutMenu ===
customScopesB?.keyboardShortcutMenu &&
customScopesA?.searchRecords === customScopesB?.searchRecords
);
};
export const useSetHotkeyScope = () =>
useRecoilCallback(
({ snapshot, set }) =>
async (hotkeyScopeToSet: string, customScopes?: CustomHotkeyScopes) => {
const currentHotkeyScope = snapshot
.getLoadable(currentHotkeyScopeState)
.getValue();
if (currentHotkeyScope.scope === hotkeyScopeToSet) {
if (!isDefined(customScopes)) {
if (
areCustomScopesEqual(
currentHotkeyScope?.customScopes,
DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES,
)
) {
return;
}
} else {
if (
areCustomScopesEqual(
currentHotkeyScope?.customScopes,
customScopes,
)
) {
return;
}
}
}
const newHotkeyScope: HotkeyScope = {
scope: hotkeyScopeToSet,
customScopes: {
commandMenu: customScopes?.commandMenu ?? true,
commandMenuOpen: customScopes?.commandMenuOpen ?? true,
goto: customScopes?.goto ?? false,
keyboardShortcutMenu: customScopes?.keyboardShortcutMenu ?? false,
searchRecords: customScopes?.searchRecords ?? false,
},
};
const scopesToSet: string[] = [];
if (newHotkeyScope.customScopes?.commandMenu === true) {
scopesToSet.push(AppHotkeyScope.CommandMenu);
}
if (newHotkeyScope.customScopes?.commandMenuOpen === true) {
scopesToSet.push(AppHotkeyScope.CommandMenuOpen);
}
if (newHotkeyScope?.customScopes?.goto === true) {
scopesToSet.push(AppHotkeyScope.Goto);
}
if (newHotkeyScope?.customScopes?.keyboardShortcutMenu === true) {
scopesToSet.push(AppHotkeyScope.KeyboardShortcutMenu);
}
if (newHotkeyScope?.customScopes?.searchRecords === true) {
scopesToSet.push(AppHotkeyScope.SearchRecords);
}
scopesToSet.push(newHotkeyScope.scope);
if (DEBUG_HOTKEY_SCOPE) {
logDebug(`DEBUG: set new hotkey scope : ${newHotkeyScope.scope}`, {
scopesToSet,
newHotkeyScope,
});
}
set(internalHotkeysEnabledScopesState, scopesToSet);
set(currentHotkeyScopeState, newHotkeyScope);
},
[],
);
@@ -1,8 +0,0 @@
import { createState } from 'twenty-ui/utilities';
import { INITIAL_HOTKEYS_SCOPE } from '../../constants/InitialHotkeysScope';
import { HotkeyScope } from '../../types/HotkeyScope';
export const currentHotkeyScopeState = createState<HotkeyScope>({
key: 'currentHotkeyScopeState',
defaultValue: INITIAL_HOTKEYS_SCOPE,
});
@@ -1,5 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const internalHotkeysEnabledScopesState = createState<string[]>({
key: 'internalHotkeysEnabledScopesState',
defaultValue: [],
});
@@ -1,11 +0,0 @@
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
import { HotkeyScope } from '../../types/HotkeyScope';
export const previousHotkeyScopeFamilyState = createFamilyState<
HotkeyScope | null,
string
>({
key: 'previousHotkeyScopeFamilyState',
defaultValue: null,
});
@@ -1,9 +0,0 @@
export enum AppHotkeyScope {
App = 'app',
Goto = 'goto',
CommandMenu = 'command-menu',
CommandMenuOpen = 'command-menu-open',
SearchRecords = 'search-records',
KeyboardShortcutMenu = 'keyboard-shortcut-menu',
KeyboardShortcutMenuOpen = 'keyboard-shortcut-menu-open',
}
@@ -1,7 +0,0 @@
export type CustomHotkeyScopes = {
goto?: boolean;
commandMenu?: boolean;
commandMenuOpen?: boolean;
keyboardShortcutMenu?: boolean;
searchRecords?: boolean;
};
@@ -1,6 +0,0 @@
import { CustomHotkeyScopes } from './CustomHotkeyScope';
export type HotkeyScope = {
scope: string;
customScopes?: CustomHotkeyScopes;
};