diff --git a/src/renderer/handlers/keyboard.test.ts b/src/renderer/handlers/keyboard.test.ts index 553780c..1fb5ce5 100644 --- a/src/renderer/handlers/keyboard.test.ts +++ b/src/renderer/handlers/keyboard.test.ts @@ -10,6 +10,7 @@ type CommandEventDetail = { visible?: boolean; key?: string; code?: string; + repeat?: boolean; }; function createClassList() { @@ -375,6 +376,36 @@ test('keyboard mode: up/down/j/k forward keydown to yomitan popup when open', as } }); +test('keyboard mode: repeated popup navigation keys are forwarded while popup is open', async () => { + const { ctx, handlers, testGlobals } = createKeyboardHandlerHarness(); + + try { + await handlers.setupMpvInputForwarding(); + handlers.handleKeyboardModeToggleRequested(); + + ctx.state.yomitanPopupVisible = true; + testGlobals.setPopupVisible(true); + + testGlobals.dispatchKeydown({ key: 'j', code: 'KeyJ', repeat: true }); + testGlobals.dispatchKeydown({ key: 'ArrowDown', code: 'ArrowDown', repeat: true }); + + const forwarded = testGlobals.commandEvents.filter( + (event) => event.type === 'forwardKeyDown', + ); + assert.equal(forwarded.length, 2); + assert.deepEqual( + forwarded.map((event) => ({ code: event.code, repeat: event.repeat })), + [ + { code: 'KeyJ', repeat: true }, + { code: 'ArrowDown', repeat: true }, + ], + ); + } finally { + ctx.state.keyboardDrivenModeEnabled = false; + testGlobals.restore(); + } +}); + test('keyboard mode: h moves left when popup is closed', async () => { const { ctx, handlers, testGlobals } = createKeyboardHandlerHarness(); diff --git a/src/renderer/handlers/keyboard.ts b/src/renderer/handlers/keyboard.ts index 64f0b13..87f3f37 100644 --- a/src/renderer/handlers/keyboard.ts +++ b/src/renderer/handlers/keyboard.ts @@ -446,7 +446,6 @@ export function createKeyboardHandlers( } function handleYomitanPopupKeybind(e: KeyboardEvent): boolean { - if (e.repeat) return false; const modifierOnlyCodes = new Set([ 'ShiftLeft', 'ShiftRight', @@ -460,6 +459,7 @@ export function createKeyboardHandlers( if (modifierOnlyCodes.has(e.code)) return false; if (!e.ctrlKey && !e.metaKey && !e.altKey && e.code === 'KeyM') { + if (e.repeat) return false; dispatchYomitanPopupMineSelected(); return true; } diff --git a/vendor/yomitan/js/display/popup-main.js b/vendor/yomitan/js/display/popup-main.js index d4d4f4e..cf0edab 100644 --- a/vendor/yomitan/js/display/popup-main.js +++ b/vendor/yomitan/js/display/popup-main.js @@ -48,7 +48,7 @@ await Application.main(true, async (application) => { displayResizer.prepare(); document.addEventListener('keydown', (event) => { - if (event.defaultPrevented || event.repeat) { return; } + if (event.defaultPrevented) { return; } if (event.ctrlKey || event.metaKey || event.altKey) { return; } const target = /** @type {?Element} */ (event.target instanceof Element ? event.target : null); @@ -59,29 +59,40 @@ await Application.main(true, async (application) => { } const code = event.code; - if (code === 'KeyJ' || code === 'KeyK') { + const isPopupScrollKey = + code === 'KeyJ' || + code === 'KeyK' || + code === 'ArrowDown' || + code === 'ArrowUp'; + if (isPopupScrollKey) { const scanningOptions = display.getOptions()?.scanning; const scale = Number.isFinite(scanningOptions?.reducedMotionScrollingScale) ? scanningOptions.reducedMotionScrollingScale : 1; - display._scrollByPopupHeight(code === 'KeyJ' ? 1 : -1, scale); + display._scrollByPopupHeight( + code === 'KeyJ' || code === 'ArrowDown' ? 1 : -1, + scale, + ); event.preventDefault(); return; } if (code === 'KeyM') { + if (event.repeat) { return; } displayAnki._hotkeySaveAnkiNoteForSelectedEntry('0'); event.preventDefault(); return; } if (code === 'KeyP') { + if (event.repeat) { return; } void displayAudio.playAudio(display.selectedIndex, 0); event.preventDefault(); return; } if (code === 'BracketLeft' || code === 'BracketRight') { + if (event.repeat) { return; } displayAudio._onMessageCycleAudioSource({direction: code === 'BracketLeft' ? 1 : -1}); event.preventDefault(); }