From 1eb284c87f50ea3396bc3617884ad4c18260179b Mon Sep 17 00:00:00 2001 From: nitin <142569587+ehconitin@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:00:51 +0530 Subject: [PATCH] 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 --- .../components/CommandMenuItemNumberInput.tsx | 23 +++++++++---------- .../components/CommandMenuItemTextInput.tsx | 22 ++++++++---------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemNumberInput.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemNumberInput.tsx index 43923fc130..f05307db2b 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemNumberInput.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemNumberInput.tsx @@ -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) => { diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemTextInput.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemTextInput.tsx index 3b7df12906..53e2e0cb02 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemTextInput.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuItemTextInput.tsx @@ -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(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({ focusId, inputRef: inputRef, inputValue: draftValue, onEscape: handleEscape, onEnter: handleEnter, - onClickOutside: isTextInputCurrentlyFocused - ? handleClickOutside - : undefined, }); const focusInput = () => {