mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-17 00:18:41 -07:00
fix(anki): keep media timing review keyboard-focused
- Route modal keydown events before later modal handlers - Restore focus and preserve visible focus styling
This commit is contained in:
@@ -452,6 +452,7 @@ function createKeyboardHandlerHarness() {
|
|||||||
const testGlobals = installKeyboardTestGlobals();
|
const testGlobals = installKeyboardTestGlobals();
|
||||||
const subtitleRootClassList = createClassList();
|
const subtitleRootClassList = createClassList();
|
||||||
const subtitleContainerClassList = createClassList();
|
const subtitleContainerClassList = createClassList();
|
||||||
|
let mediaTimingReviewKeydownCount = 0;
|
||||||
let controllerSelectKeydownCount = 0;
|
let controllerSelectKeydownCount = 0;
|
||||||
let openControllerSelectCount = 0;
|
let openControllerSelectCount = 0;
|
||||||
let openControllerDebugCount = 0;
|
let openControllerDebugCount = 0;
|
||||||
@@ -494,7 +495,10 @@ function createKeyboardHandlerHarness() {
|
|||||||
handleKikuKeydown: () => false,
|
handleKikuKeydown: () => false,
|
||||||
handleJimakuKeydown: () => false,
|
handleJimakuKeydown: () => false,
|
||||||
handleTsukihimeKeydown: () => false,
|
handleTsukihimeKeydown: () => false,
|
||||||
handleMediaTimingReviewKeydown: () => false,
|
handleMediaTimingReviewKeydown: () => {
|
||||||
|
mediaTimingReviewKeydownCount += 1;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
handleControllerSelectKeydown: () => {
|
handleControllerSelectKeydown: () => {
|
||||||
controllerSelectKeydownCount += 1;
|
controllerSelectKeydownCount += 1;
|
||||||
return true;
|
return true;
|
||||||
@@ -524,6 +528,7 @@ function createKeyboardHandlerHarness() {
|
|||||||
ctx,
|
ctx,
|
||||||
handlers,
|
handlers,
|
||||||
testGlobals,
|
testGlobals,
|
||||||
|
mediaTimingReviewKeydownCount: () => mediaTimingReviewKeydownCount,
|
||||||
controllerSelectKeydownCount: () => controllerSelectKeydownCount,
|
controllerSelectKeydownCount: () => controllerSelectKeydownCount,
|
||||||
openControllerSelectCount: () => openControllerSelectCount,
|
openControllerSelectCount: () => openControllerSelectCount,
|
||||||
openControllerDebugCount: () => openControllerDebugCount,
|
openControllerDebugCount: () => openControllerDebugCount,
|
||||||
@@ -1368,6 +1373,29 @@ test('keyboard mode: controller select modal handles arrow keys before yomitan p
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('media timing review modal handles keys before later modal handlers', async () => {
|
||||||
|
const {
|
||||||
|
ctx,
|
||||||
|
testGlobals,
|
||||||
|
handlers,
|
||||||
|
mediaTimingReviewKeydownCount,
|
||||||
|
controllerSelectKeydownCount,
|
||||||
|
} = createKeyboardHandlerHarness();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await handlers.setupMpvInputForwarding();
|
||||||
|
ctx.state.mediaTimingReviewModalOpen = true;
|
||||||
|
ctx.state.controllerSelectModalOpen = true;
|
||||||
|
|
||||||
|
testGlobals.dispatchKeydown({ key: 'ArrowDown', code: 'ArrowDown' });
|
||||||
|
|
||||||
|
assert.equal(mediaTimingReviewKeydownCount(), 1);
|
||||||
|
assert.equal(controllerSelectKeydownCount(), 0);
|
||||||
|
} finally {
|
||||||
|
testGlobals.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('keyboard mode: playlist browser modal handles arrow keys before yomitan popup', async () => {
|
test('keyboard mode: playlist browser modal handles arrow keys before yomitan popup', async () => {
|
||||||
const { ctx, testGlobals, handlers, playlistBrowserKeydownCount } =
|
const { ctx, testGlobals, handlers, playlistBrowserKeydownCount } =
|
||||||
createKeyboardHandlerHarness();
|
createKeyboardHandlerHarness();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { MediaTimingReviewDecision, MediaTimingReviewOpenPayload } from '../../types/anki';
|
import type { MediaTimingReviewDecision, MediaTimingReviewOpenPayload } from '../../types/anki';
|
||||||
import type { ModalStateReader, RendererContext } from '../context';
|
import type { ModalStateReader, RendererContext } from '../context';
|
||||||
|
import { createModalFocusGuard } from './modal-focus-guard';
|
||||||
|
|
||||||
const MINIMUM_CLIP_SECONDS = 0.1;
|
const MINIMUM_CLIP_SECONDS = 0.1;
|
||||||
const FINE_ADJUST_SECONDS = 0.1;
|
const FINE_ADJUST_SECONDS = 0.1;
|
||||||
@@ -139,6 +140,17 @@ export function createMediaTimingReviewModal(
|
|||||||
trackWidth: number;
|
trackWidth: number;
|
||||||
grabOffset: number;
|
grabOffset: number;
|
||||||
} | null = null;
|
} | null = null;
|
||||||
|
|
||||||
|
const focus = createModalFocusGuard({
|
||||||
|
isOpen: () => ctx.state.mediaTimingReviewModalOpen,
|
||||||
|
getModalRoot: () => ctx.dom.mediaTimingReviewModal,
|
||||||
|
getPreferredFocusTargets: () => [
|
||||||
|
ctx.dom.mediaTimingReviewStartHandle,
|
||||||
|
ctx.dom.mediaTimingReviewCancelBack,
|
||||||
|
],
|
||||||
|
getFallbackFocusTarget: () => ctx.dom.mediaTimingReviewCancel,
|
||||||
|
isModalLayer: ctx.platform.isModalLayer,
|
||||||
|
});
|
||||||
const previewRequest = createMediaTimingPreviewRequestGuard();
|
const previewRequest = createMediaTimingPreviewRequestGuard();
|
||||||
|
|
||||||
function setStatus(message: string, isError = false): void {
|
function setStatus(message: string, isError = false): void {
|
||||||
@@ -453,6 +465,7 @@ export function createMediaTimingReviewModal(
|
|||||||
ctx.state.mediaTimingReviewModalOpen = false;
|
ctx.state.mediaTimingReviewModalOpen = false;
|
||||||
ctx.dom.mediaTimingReviewModal.classList.add('hidden');
|
ctx.dom.mediaTimingReviewModal.classList.add('hidden');
|
||||||
ctx.dom.mediaTimingReviewModal.setAttribute('aria-hidden', 'true');
|
ctx.dom.mediaTimingReviewModal.setAttribute('aria-hidden', 'true');
|
||||||
|
focus.detach();
|
||||||
window.electronAPI.notifyOverlayModalClosed('media-timing-review');
|
window.electronAPI.notifyOverlayModalClosed('media-timing-review');
|
||||||
options.syncSettingsModalSubtitleSuppression();
|
options.syncSettingsModalSubtitleSuppression();
|
||||||
payload = null;
|
payload = null;
|
||||||
@@ -566,7 +579,10 @@ export function createMediaTimingReviewModal(
|
|||||||
ctx.dom.mediaTimingReviewModal.classList.remove('hidden');
|
ctx.dom.mediaTimingReviewModal.classList.remove('hidden');
|
||||||
ctx.dom.mediaTimingReviewModal.setAttribute('aria-hidden', 'false');
|
ctx.dom.mediaTimingReviewModal.setAttribute('aria-hidden', 'false');
|
||||||
window.electronAPI.notifyOverlayModalOpened('media-timing-review');
|
window.electronAPI.notifyOverlayModalOpened('media-timing-review');
|
||||||
ctx.dom.mediaTimingReviewStartHandle.focus();
|
focus.attach();
|
||||||
|
focus.requestOverlayFocus();
|
||||||
|
window.focus();
|
||||||
|
focus.enforceModalFocus();
|
||||||
queueWaveformLoad();
|
queueWaveformLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1711,7 +1711,7 @@ body:focus-visible,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.media-timing-review-handle:focus-visible {
|
.media-timing-review-handle:focus-visible {
|
||||||
outline: none;
|
outline: 2px solid transparent;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 0 0 0 2px var(--ctp-yellow),
|
inset 0 0 0 2px var(--ctp-yellow),
|
||||||
0 0 16px color-mix(in srgb, var(--ctp-yellow) 55%, transparent);
|
0 0 16px color-mix(in srgb, var(--ctp-yellow) 55%, transparent);
|
||||||
|
|||||||
Reference in New Issue
Block a user