mirror of
https://github.com/cpvalente/ontime.git
synced 2026-07-26 10:38:55 +00:00
fix(cuesheet): prevent timer cells from submitting on tab-out or escape (#2083)
This commit is contained in:
@@ -3,8 +3,8 @@ import { ChangeEvent, KeyboardEvent, RefObject, useCallback, useEffect, useMemo,
|
||||
|
||||
interface UseReactiveTextInputReturn {
|
||||
value: string;
|
||||
onChange: (event: ChangeEvent) => void;
|
||||
onBlur: (event: ChangeEvent) => void;
|
||||
onChange: (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
||||
onBlur: (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
||||
onKeyDown: (event: KeyboardEvent<HTMLElement>) => void;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ export default function useReactiveTextInput(
|
||||
options?: {
|
||||
submitOnEnter?: boolean;
|
||||
submitOnCtrlEnter?: boolean;
|
||||
submitOnTab?: boolean;
|
||||
onCancelUpdate?: () => void;
|
||||
onTabCancel?: () => void;
|
||||
allowSubmitSameValue?: boolean;
|
||||
allowKeyboardNavigation?: boolean;
|
||||
},
|
||||
@@ -23,6 +25,10 @@ export default function useReactiveTextInput(
|
||||
const [text, setText] = useState<string>(initialText);
|
||||
// track whether we are submitting via a submit key (eg enter) and avoid submitting again on blur
|
||||
const isKeyboardSubmitting = useRef(false);
|
||||
// track escape to prevent the subsequent blur from submitting
|
||||
const isEscaping = useRef(false);
|
||||
// track tab-out to prevent blur from submitting (used by timer cells)
|
||||
const isTabbing = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof initialText === 'undefined') {
|
||||
@@ -78,6 +84,7 @@ export default function useReactiveTextInput(
|
||||
* @param {string} valueToSubmit
|
||||
*/
|
||||
const handleEscape = useCallback(() => {
|
||||
isEscaping.current = true;
|
||||
// No need to update if it hasn't changed
|
||||
setText(initialText);
|
||||
// force the text to be the initial value
|
||||
@@ -141,16 +148,30 @@ export default function useReactiveTextInput(
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
// for cells that opt out of tab-submit, track tab-out without preventing navigation
|
||||
if (event.key === 'Tab' && options?.submitOnTab === false) {
|
||||
isTabbing.current = true;
|
||||
}
|
||||
|
||||
hotKeyHandler(event);
|
||||
};
|
||||
}, [handleEscape, handleSubmit, options?.submitOnCtrlEnter, options?.submitOnEnter, text]);
|
||||
}, [handleEscape, handleSubmit, options?.submitOnCtrlEnter, options?.submitOnEnter, options?.submitOnTab, text]);
|
||||
|
||||
return {
|
||||
value: text,
|
||||
onChange: (event: ChangeEvent) => handleChange((event.target as HTMLInputElement).value),
|
||||
onBlur: (event: ChangeEvent) => {
|
||||
onChange: (event) => handleChange(event.target.value),
|
||||
onBlur: (event) => {
|
||||
if (isTabbing.current) {
|
||||
isTabbing.current = false;
|
||||
(options?.onTabCancel ?? options?.onCancelUpdate)?.();
|
||||
return;
|
||||
}
|
||||
if (isEscaping.current) {
|
||||
isEscaping.current = false;
|
||||
return;
|
||||
}
|
||||
if (!isKeyboardSubmitting.current) {
|
||||
handleSubmit((event.target as HTMLInputElement).value);
|
||||
handleSubmit(event.target.value);
|
||||
}
|
||||
},
|
||||
onKeyDown: keyHandler,
|
||||
|
||||
+6
@@ -49,6 +49,10 @@ function DurationInput({
|
||||
setIsEditing(false);
|
||||
setTimeout(() => textRef.current?.focusParentElement()); // Immediate timeout to ensure state change takes place first
|
||||
};
|
||||
const handleTabOut = useCallback(() => {
|
||||
setIsEditing(false);
|
||||
// Tab moves focus naturally; do not steal it back to parent
|
||||
}, []);
|
||||
|
||||
const handleUpdate = useCallback(
|
||||
(newValue: string) => {
|
||||
@@ -95,8 +99,10 @@ function DurationInput({
|
||||
ref={inputRef}
|
||||
initialValue={timeString}
|
||||
allowSubmitSameValue={!lockedValue} // if the value is not locked, submitting will lock the value
|
||||
submitOnTab={false}
|
||||
handleUpdate={handleUpdate}
|
||||
handleCancelUpdate={handleFakeBlur}
|
||||
handleTabCancel={handleTabOut}
|
||||
/>
|
||||
) : (
|
||||
<TextLikeInput
|
||||
|
||||
+14
-1
@@ -8,13 +8,24 @@ interface SingleLineCellProps {
|
||||
fieldId?: string;
|
||||
fieldLabel?: string;
|
||||
allowSubmitSameValue?: boolean;
|
||||
submitOnTab?: boolean;
|
||||
handleUpdate: (newValue: string) => void;
|
||||
handleCancelUpdate?: () => void;
|
||||
handleTabCancel?: () => void;
|
||||
}
|
||||
|
||||
const SingleLineCell = forwardRef(
|
||||
(
|
||||
{ initialValue, fieldId, fieldLabel, allowSubmitSameValue, handleUpdate, handleCancelUpdate }: SingleLineCellProps,
|
||||
{
|
||||
initialValue,
|
||||
fieldId,
|
||||
fieldLabel,
|
||||
allowSubmitSameValue,
|
||||
submitOnTab,
|
||||
handleUpdate,
|
||||
handleCancelUpdate,
|
||||
handleTabCancel,
|
||||
}: SingleLineCellProps,
|
||||
inputRef,
|
||||
) => {
|
||||
const ref = useRef<HTMLInputElement | null>(null);
|
||||
@@ -25,7 +36,9 @@ const SingleLineCell = forwardRef(
|
||||
allowKeyboardNavigation: true,
|
||||
submitOnEnter: true, // single line should submit on enter
|
||||
submitOnCtrlEnter: true,
|
||||
submitOnTab,
|
||||
onCancelUpdate: handleCancelUpdate,
|
||||
onTabCancel: handleTabCancel,
|
||||
});
|
||||
|
||||
// expose a subset of the methods to the parent
|
||||
|
||||
@@ -50,6 +50,10 @@ function TimeInputDuration({
|
||||
setIsEditing(false);
|
||||
setTimeout(() => textRef.current?.focusParentElement()); // Immediate timeout to ensure state change takes place first
|
||||
};
|
||||
const handleTabOut = useCallback(() => {
|
||||
setIsEditing(false);
|
||||
// Tab moves focus naturally; do not steal it back to parent
|
||||
}, []);
|
||||
|
||||
const handleUpdate = useCallback(
|
||||
(newValue: string) => {
|
||||
@@ -96,8 +100,10 @@ function TimeInputDuration({
|
||||
ref={inputRef}
|
||||
initialValue={timeString}
|
||||
allowSubmitSameValue={!lockedValue} // if the value is not locked, submitting will lock the value
|
||||
submitOnTab={false}
|
||||
handleUpdate={handleUpdate}
|
||||
handleCancelUpdate={handleFakeBlur}
|
||||
handleTabCancel={handleTabOut}
|
||||
/>
|
||||
) : (
|
||||
<TextLikeInput
|
||||
|
||||
Reference in New Issue
Block a user