Fix command menu text/number inputs to commit on blur and cancel cleanly on Escape (#18283)

closes https://github.com/twentyhq/twenty/issues/18264




https://github.com/user-attachments/assets/7b576a00-78bc-46a2-9528-d8b3bcbdd530




https://github.com/user-attachments/assets/4102468e-e85f-46a0-8b23-e7abd77bfc95



### PR description -
This fixes flaky persistence in command menu text and number inputs.

- moved commit logic to onBlur (single commit path)
- Enter now blurs, so it uses the same commit path
- Escape now cancels edit (restores draft + exits) without persisting
- removed dependency on input click-outside commit timing

### Outcome -

- clicking anywhere outside the input now reliably persists edits
- Escape consistently discards edits
This commit is contained in:
nitin
2026-03-02 21:00:51 +05:30
committed by GitHub
parent c4140f85df
commit 1eb284c87f
2 changed files with 21 additions and 24 deletions
@@ -4,9 +4,9 @@ import { TextInput } from '@/ui/input/components/TextInput';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
import { currentFocusIdSelector } from '@/ui/utilities/focus/states/currentFocusIdSelector';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import styled from '@emotion/styled';
import { useStore } from 'jotai';
import { useRef, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { type IconComponent } from 'twenty-ui/display';
@@ -44,9 +44,7 @@ export const CommandMenuItemNumberInput = ({
const focusId = `${id}-input`;
const [draftValue, setDraftValue] = useState(value);
const [hasError, setHasError] = useState(false);
const currentFocusId = useAtomStateValue(currentFocusIdSelector);
const isNumberInputCurrentlyFocused = currentFocusId === focusId;
const store = useStore();
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const { removeFocusItemFromFocusStackById } =
@@ -87,20 +85,24 @@ export const CommandMenuItemNumberInput = ({
};
const handleBlur = () => {
const isInputStillFocused =
store.get(currentFocusIdSelector.atom) === focusId;
if (isInputStillFocused && draftValue !== value) {
handleCommit(draftValue);
}
removeFocusItemFromFocusStackById({ focusId });
};
const handleEscape = () => {
removeFocusItemFromFocusStackById({ focusId });
setDraftValue(value);
setHasError(false);
inputRef.current?.blur();
};
const handleClickOutside = () => {
handleCommit(draftValue);
};
const handleEnter = () => {
handleCommit(draftValue);
inputRef.current?.blur();
};
@@ -110,9 +112,6 @@ export const CommandMenuItemNumberInput = ({
inputValue: draftValue,
onEscape: handleEscape,
onEnter: handleEnter,
onClickOutside: isNumberInputCurrentlyFocused
? handleClickOutside
: undefined,
});
const handleChange = (text: string) => {
@@ -4,9 +4,9 @@ import { TextInput } from '@/ui/input/components/TextInput';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
import { currentFocusIdSelector } from '@/ui/utilities/focus/states/currentFocusIdSelector';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import styled from '@emotion/styled';
import { useStore } from 'jotai';
import { useRef, useState } from 'react';
import { type IconComponent } from 'twenty-ui/display';
@@ -36,9 +36,7 @@ export const CommandMenuItemTextInput = ({
const inputRef = useRef<HTMLInputElement>(null);
const focusId = `${id}-input`;
const [draftValue, setDraftValue] = useState(value);
const currentFocusId = useAtomStateValue(currentFocusIdSelector);
const isTextInputCurrentlyFocused = currentFocusId === focusId;
const store = useStore();
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const { removeFocusItemFromFocusStackById } =
@@ -59,32 +57,32 @@ export const CommandMenuItemTextInput = ({
};
const handleBlur = () => {
const isInputStillFocused =
store.get(currentFocusIdSelector.atom) === focusId;
if (isInputStillFocused && draftValue !== value) {
onChange(draftValue);
}
removeFocusItemFromFocusStackById({ focusId });
};
const handleEscape = () => {
removeFocusItemFromFocusStackById({ focusId });
setDraftValue(value);
inputRef.current?.blur();
};
const handleEnter = () => {
onChange(draftValue);
inputRef.current?.blur();
};
const handleClickOutside = () => {
onChange(draftValue);
};
useRegisterInputEvents<string>({
focusId,
inputRef: inputRef,
inputValue: draftValue,
onEscape: handleEscape,
onEnter: handleEnter,
onClickOutside: isTextInputCurrentlyFocused
? handleClickOutside
: undefined,
});
const focusInput = () => {