fix(twenty-front): properly manage focus stack in useInlineCell hook (#16309)
The useInlineCell hook was only managing the dropdown focus state (activeDropdownFocusIdState/previousDropdownFocusIdState) but not the focus stack (focusStackState). Since the hotkey system checks currentFocusIdSelector which reads from focusStackState, closing an inline cell in the side panel would leave the focus stack in an incorrect state, causing hotkeys to not work properly. This fix adds proper focus stack management: - openInlineCell: pushes the inline cell to the focus stack - closeInlineCell: removes the inline cell from the focus stack Fixes #16224 ### Tests Added - Added unit tests for [useInlineCell](cci:1://file:///Users/apple/WebstormProjects/twenty/packages/twenty-front/src/modules/object-record/record-inline-cell/hooks/useInlineCell.ts:15:0-83:2) hook to verify focus stack management - Tests cover: opening inline cell, closing inline cell, and full open/close cycles - Tests ensure `focusStackState` and `activeDropdownFocusIdState` are properly synchronized --------- Co-authored-by: Joker <apple@Apples-MacBook-Pro.local> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
+6
-1
@@ -43,7 +43,12 @@ export const RecordFieldListCellEditModePortal = ({
|
||||
recordId={recordId}
|
||||
instanceIdPrefix={instanceId}
|
||||
>
|
||||
<RecordFieldListInputContextProvider>
|
||||
<RecordFieldListInputContextProvider
|
||||
fieldMetadataItem={editedFieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={recordId}
|
||||
instanceIdPrefix={instanceId}
|
||||
>
|
||||
<RecordInlineCellEditMode>
|
||||
<FieldInput />
|
||||
</RecordInlineCellEditMode>
|
||||
|
||||
+6
-1
@@ -42,7 +42,12 @@ export const RecordFieldListCellHoveredPortal = ({
|
||||
recordId={recordId}
|
||||
instanceIdPrefix={instanceId}
|
||||
>
|
||||
<RecordFieldListInputContextProvider>
|
||||
<RecordFieldListInputContextProvider
|
||||
fieldMetadataItem={hoveredFieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={recordId}
|
||||
instanceIdPrefix={instanceId}
|
||||
>
|
||||
<RecordFieldListCellHoveredPortalContent />
|
||||
</RecordFieldListInputContextProvider>
|
||||
</RecordInlineCellAnchoredPortal>
|
||||
|
||||
+35
-7
@@ -1,13 +1,16 @@
|
||||
import { usePersistFieldFromFieldInputContext } from '@/object-record/record-field/ui/hooks/usePersistFieldFromFieldInputContext';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
|
||||
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
|
||||
import { recordFieldListCellEditModePositionComponentState } from '@/object-record/record-field-list/states/recordFieldListCellEditModePositionComponentState';
|
||||
import {
|
||||
FieldInputEventContext,
|
||||
type FieldInputClickOutsideEvent,
|
||||
type FieldInputEvent,
|
||||
} from '@/object-record/record-field/ui/contexts/FieldInputEventContext';
|
||||
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
||||
import { useOpenFieldInputEditMode } from '@/object-record/record-field/ui/hooks/useOpenFieldInputEditMode';
|
||||
import { currentFocusIdSelector } from '@/ui/utilities/focus/states/currentFocusIdSelector';
|
||||
import { useAvailableComponentInstanceId } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceId';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
@@ -16,24 +19,49 @@ import { useRecoilCallback } from 'recoil';
|
||||
|
||||
type RecordFieldListInputContextProviderProps = {
|
||||
children: React.ReactNode;
|
||||
recordId: string;
|
||||
fieldMetadataItem: FieldMetadataItem;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
instanceIdPrefix: string;
|
||||
};
|
||||
|
||||
export const RecordFieldListInputContextProvider = ({
|
||||
children,
|
||||
recordId,
|
||||
fieldMetadataItem,
|
||||
objectMetadataItem,
|
||||
instanceIdPrefix,
|
||||
}: RecordFieldListInputContextProviderProps) => {
|
||||
const { closeInlineCell } = useInlineCell();
|
||||
const instanceId = useAvailableComponentInstanceId(
|
||||
RecordFieldComponentInstanceContext,
|
||||
);
|
||||
|
||||
const { closeFieldInput } = useOpenFieldInputEditMode();
|
||||
|
||||
const setRecordFieldListCellEditModePosition = useSetRecoilComponentState(
|
||||
recordFieldListCellEditModePositionComponentState,
|
||||
);
|
||||
|
||||
const fieldDefinition = formatFieldMetadataItemAsFieldDefinition({
|
||||
field: fieldMetadataItem,
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const closeInlineCellAndResetEditModePosition = useCallback(() => {
|
||||
setRecordFieldListCellEditModePosition(null);
|
||||
closeInlineCell();
|
||||
}, [closeInlineCell, setRecordFieldListCellEditModePosition]);
|
||||
|
||||
const instanceId = useAvailableComponentInstanceId(
|
||||
RecordFieldComponentInstanceContext,
|
||||
);
|
||||
closeFieldInput({
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
prefix: instanceIdPrefix,
|
||||
});
|
||||
}, [
|
||||
setRecordFieldListCellEditModePosition,
|
||||
closeFieldInput,
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
instanceIdPrefix,
|
||||
]);
|
||||
|
||||
const { persistFieldFromFieldInputContext } =
|
||||
usePersistFieldFromFieldInputContext();
|
||||
|
||||
-1
@@ -33,7 +33,6 @@ export const useInlineCell = (
|
||||
|
||||
const closeInlineCell = () => {
|
||||
onCloseEditMode?.();
|
||||
|
||||
goBackToPreviousDropdownFocusId();
|
||||
};
|
||||
|
||||
|
||||
+6
-1
@@ -46,7 +46,12 @@ export const FieldsWidgetCellEditModePortal = ({
|
||||
recordId={recordId}
|
||||
instanceIdPrefix={instanceId}
|
||||
>
|
||||
<RecordFieldListInputContextProvider>
|
||||
<RecordFieldListInputContextProvider
|
||||
fieldMetadataItem={editedFieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={recordId}
|
||||
instanceIdPrefix={instanceId}
|
||||
>
|
||||
<RecordInlineCellEditMode>
|
||||
<FieldInput />
|
||||
</RecordInlineCellEditMode>
|
||||
|
||||
+6
-1
@@ -45,7 +45,12 @@ export const FieldsWidgetCellHoveredPortal = ({
|
||||
recordId={recordId}
|
||||
instanceIdPrefix={instanceId}
|
||||
>
|
||||
<RecordFieldListInputContextProvider>
|
||||
<RecordFieldListInputContextProvider
|
||||
fieldMetadataItem={hoveredFieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={recordId}
|
||||
instanceIdPrefix={instanceId}
|
||||
>
|
||||
<RecordFieldListCellHoveredPortalContent />
|
||||
</RecordFieldListInputContextProvider>
|
||||
</RecordInlineCellAnchoredPortal>
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ export const useGlobalHotkeysCallback = (
|
||||
if (preventDefault === true) {
|
||||
if (DEBUG_FOCUS_STACK) {
|
||||
logDebug(
|
||||
`DEBUG: %cI prevent default for hotkey (${hotkeysEvent.keys})`,
|
||||
`DEBUG: %cI prevent global default for hotkey (${hotkeysEvent.keys})`,
|
||||
'color: gray;',
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user