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:
+11
-12
@@ -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) => {
|
||||
|
||||
+10
-12
@@ -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 = () => {
|
||||
|
||||
Reference in New Issue
Block a user