refactor: configure Hachidori in the SubMiner build

This commit is contained in:
2026-09-22 12:26:37 -07:00
parent 5354fdf7b9
commit d7e1d7edc6
10 changed files with 113 additions and 21 deletions
+3 -4
View File
@@ -2,8 +2,7 @@ import type { CompiledSessionBinding, PrimarySubMode, ShortcutsConfig } from '..
import type { RendererContext } from '../context';
import { createMpvInputForwarding } from './mpv-input-forwarding';
import {
YOMITAN_POPUP_HIDDEN_EVENT,
YOMITAN_POPUP_SHOWN_EVENT,
registerDictionaryPopupVisibilityListener,
YOMITAN_POPUP_COMMAND_EVENT,
isYomitanPopupVisible,
isYomitanPopupIframe,
@@ -1064,7 +1063,7 @@ export function createKeyboardHandlers(
subtree: true,
});
window.addEventListener(YOMITAN_POPUP_HIDDEN_EVENT, () => {
registerDictionaryPopupVisibilityListener('hidden', () => {
clearNativeSubtitleSelection();
if (!ctx.state.keyboardDrivenModeEnabled) {
syncKeyboardTokenSelection();
@@ -1072,7 +1071,7 @@ export function createKeyboardHandlers(
}
restoreOverlayKeyboardFocus();
});
window.addEventListener(YOMITAN_POPUP_SHOWN_EVENT, () => {
registerDictionaryPopupVisibilityListener('shown', () => {
if (!ctx.state.keyboardDrivenModeEnabled) {
return;
}
+3 -4
View File
@@ -1,10 +1,9 @@
import type { ModalStateReader, RendererContext } from '../context';
import { syncOverlayMouseIgnoreState } from '../overlay-mouse-ignore.js';
import {
YOMITAN_POPUP_HIDDEN_EVENT,
YOMITAN_POPUP_MOUSE_ENTER_EVENT,
YOMITAN_POPUP_MOUSE_LEAVE_EVENT,
YOMITAN_POPUP_SHOWN_EVENT,
registerDictionaryPopupVisibilityListener,
PRIMARY_SUB_VISIBLE_ON_YOMITAN_POPUP_CLASS,
isYomitanPopupVisible,
isYomitanPopupIframe,
@@ -470,7 +469,7 @@ export function createMouseHandlers(
function setupYomitanObserver(): void {
reconcilePopupInteraction({ allowPause: true });
window.addEventListener(YOMITAN_POPUP_SHOWN_EVENT, () => {
registerDictionaryPopupVisibilityListener('shown', () => {
reconcilePopupInteraction({
assumeVisible: true,
allowPause: true,
@@ -478,7 +477,7 @@ export function createMouseHandlers(
});
});
window.addEventListener(YOMITAN_POPUP_HIDDEN_EVENT, () => {
registerDictionaryPopupVisibilityListener('hidden', () => {
disablePopupInteractionIfIdle();
});
+11 -6
View File
@@ -12,8 +12,7 @@ import {
hasSubtitleSidebarSelection,
} from './subtitle-sidebar-selection.js';
import {
YOMITAN_POPUP_HIDDEN_EVENT,
YOMITAN_POPUP_SHOWN_EVENT,
registerDictionaryPopupVisibilityListener,
isYomitanPopupVisible,
} from '../yomitan-popup.js';
@@ -823,12 +822,18 @@ export function createSubtitleSidebarModal(
syncEmbeddedSidebarLayout();
};
window.addEventListener('resize', resizeHandler);
window.addEventListener(YOMITAN_POPUP_SHOWN_EVENT, handleYomitanPopupShown);
window.addEventListener(YOMITAN_POPUP_HIDDEN_EVENT, handleYomitanPopupHidden);
const disposeShown = registerDictionaryPopupVisibilityListener(
'shown',
handleYomitanPopupShown,
);
const disposeHidden = registerDictionaryPopupVisibilityListener(
'hidden',
handleYomitanPopupHidden,
);
disposeDomEvents = () => {
window.removeEventListener('resize', resizeHandler);
window.removeEventListener(YOMITAN_POPUP_SHOWN_EVENT, handleYomitanPopupShown);
window.removeEventListener(YOMITAN_POPUP_HIDDEN_EVENT, handleYomitanPopupHidden);
disposeShown();
disposeHidden();
disposeDomEvents = null;
};
}
+25
View File
@@ -5,8 +5,33 @@ import {
YOMITAN_POPUP_VISIBLE_HOST_SELECTOR,
isYomitanPopupVisible,
registerYomitanLookupListener,
registerDictionaryPopupVisibilityListener,
} from './yomitan-popup.js';
test('native popup attention events from either backend have the same lifecycle', () => {
for (const backend of ['yomitan', 'hachidori']) {
const target = new EventTarget();
const calls: string[] = [];
const disposeShown = registerDictionaryPopupVisibilityListener(
'shown',
() => calls.push('shown'),
target,
);
const disposeHidden = registerDictionaryPopupVisibilityListener(
'hidden',
() => calls.push('hidden'),
target,
);
target.dispatchEvent(new CustomEvent(`${backend}-popup-shown`));
target.dispatchEvent(new CustomEvent(`${backend}-popup-hidden`));
disposeShown();
disposeHidden();
target.dispatchEvent(new CustomEvent(`${backend}-popup-shown`));
target.dispatchEvent(new CustomEvent(`${backend}-popup-hidden`));
assert.deepEqual(calls, ['shown', 'hidden']);
}
});
test('registerYomitanLookupListener forwards the SubMiner Yomitan lookup event', () => {
const target = new EventTarget();
const calls: string[] = [];
+13
View File
@@ -11,6 +11,19 @@ export const YOMITAN_POPUP_COMMAND_EVENT = 'subminer-yomitan-popup-command';
export const YOMITAN_LOOKUP_EVENT = 'subminer-yomitan-lookup';
export const PRIMARY_SUB_VISIBLE_ON_YOMITAN_POPUP_CLASS = 'primary-sub-visible-on-yomitan-popup';
// Only the active backend injects a reader. Consume its native attention events.
export function registerDictionaryPopupVisibilityListener(
state: 'shown' | 'hidden',
listener: () => void,
target: EventTarget = window,
): () => void {
const events = [`yomitan-popup-${state}`, `hachidori-popup-${state}`];
for (const event of events) target.addEventListener(event, listener);
return () => {
for (const event of events) target.removeEventListener(event, listener);
};
}
export function registerYomitanLookupListener(
target: EventTarget = window,
listener: () => void,