diff --git a/src/renderer/src/BottomBar.tsx b/src/renderer/src/BottomBar.tsx index 7d875d6a..95ba46b3 100644 --- a/src/renderer/src/BottomBar.tsx +++ b/src/renderer/src/BottomBar.tsx @@ -23,6 +23,7 @@ import getSwal from './swal'; import { getSegColor as getSegColorRaw } from './util/colors'; import { useSegColors } from './contexts'; import { isExactDurationMatch } from './util/duration'; +import handleCutTimeInputKeyDown from './util/inputFocus'; import useUserSettings from './hooks/useUserSettings'; import useActionTitle from './hooks/useActionTitle'; import { askForPlaybackRate, checkAppPath } from './dialogs'; @@ -251,6 +252,7 @@ const CutTimeInput = memo(({ disabled, darkMode, cutTime, setCutTime, startTimeO onChange={(e) => handleCutTimeInput(e.target.value)} onPaste={handleCutTimePaste} onBlur={handleInputBlur} + onKeyDown={(e) => handleCutTimeInputKeyDown(e, isStart)} onContextMenu={handleContextMenu} value={renderValue()} /> diff --git a/src/renderer/src/util/inputFocus.test.ts b/src/renderer/src/util/inputFocus.test.ts new file mode 100644 index 00000000..1e1d788d --- /dev/null +++ b/src/renderer/src/util/inputFocus.test.ts @@ -0,0 +1,37 @@ +import { expect, it, vi } from 'vitest'; + +import handleCutTimeInputKeyDown from './inputFocus'; + + +function createKeyEvent({ code, shiftKey = false }: { code: string, shiftKey?: boolean }) { + return { + code, + shiftKey, + preventDefault: vi.fn(), + currentTarget: { blur: vi.fn() }, + }; +} + +it('should release focus when tabbing forward from the end time input', () => { + const event = createKeyEvent({ code: 'Tab' }); + + handleCutTimeInputKeyDown(event, false); + + expect(event.preventDefault).toHaveBeenCalledOnce(); + expect(event.currentTarget.blur).toHaveBeenCalledOnce(); +}); + +it.each([ + { name: 'the start time input', code: 'Tab', shiftKey: false, isStart: true }, + { name: 'backwards tabbing', code: 'Tab', shiftKey: true, isStart: false }, + { name: 'enter', code: 'Enter', shiftKey: false, isStart: false }, + { name: 'escape', code: 'Escape', shiftKey: false, isStart: false }, + { name: 'text editing', code: 'Digit1', shiftKey: false, isStart: false }, +])('should preserve focus for $name', ({ code, shiftKey, isStart }) => { + const event = createKeyEvent({ code, shiftKey }); + + handleCutTimeInputKeyDown(event, isStart); + + expect(event.preventDefault).not.toHaveBeenCalled(); + expect(event.currentTarget.blur).not.toHaveBeenCalled(); +}); diff --git a/src/renderer/src/util/inputFocus.ts b/src/renderer/src/util/inputFocus.ts new file mode 100644 index 00000000..8ec72d41 --- /dev/null +++ b/src/renderer/src/util/inputFocus.ts @@ -0,0 +1,13 @@ +import type { KeyboardEvent } from 'react'; + + +type CutTimeInputKeyEvent = Pick, 'code' | 'shiftKey' | 'preventDefault'> & { + currentTarget: Pick, +}; + +export default function handleCutTimeInputKeyDown(e: CutTimeInputKeyEvent, isStart: boolean | undefined) { + if (isStart || e.code !== 'Tab' || e.shiftKey) return; + + e.preventDefault(); + e.currentTarget.blur(); +}