mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-06-17 03:13:30 -07:00
fix(overlay): restore macOS Yomitan popup focus without breaking click-away (#125)
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
type: fixed
|
||||||
|
area: overlay
|
||||||
|
|
||||||
|
- Fixed macOS Yomitan popup focus after card mining or popup reload while still allowing click-away to close the popup without a hide/reappear cycle.
|
||||||
|
- Fixed macOS Yomitan popups staying open when clicking transparent overlay space; click-away is captured for popup close, then passthrough returns to mpv.
|
||||||
@@ -5307,6 +5307,15 @@ const { registerIpcRuntimeHandlers } = composeIpcRuntimeHandlers({
|
|||||||
focusMainWindow: () => {
|
focusMainWindow: () => {
|
||||||
const mainWindow = overlayManager.getMainWindow();
|
const mainWindow = overlayManager.getMainWindow();
|
||||||
if (!mainWindow || mainWindow.isDestroyed()) return;
|
if (!mainWindow || mainWindow.isDestroyed()) return;
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
focusMacOSOverlayWindow({
|
||||||
|
platform: process.platform,
|
||||||
|
getOverlayWindow: () => mainWindow,
|
||||||
|
stealAppFocus: () => app.focus({ steal: true }),
|
||||||
|
warn: (message, details) => logger.warn(message, details),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!mainWindow.isFocused()) {
|
if (!mainWindow.isFocused()) {
|
||||||
mainWindow.focus();
|
mainWindow.focus();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { createMouseHandlers } from './mouse.js';
|
|||||||
import {
|
import {
|
||||||
YOMITAN_POPUP_HIDDEN_EVENT,
|
YOMITAN_POPUP_HIDDEN_EVENT,
|
||||||
YOMITAN_POPUP_HOST_SELECTOR,
|
YOMITAN_POPUP_HOST_SELECTOR,
|
||||||
|
YOMITAN_POPUP_MOUSE_ENTER_EVENT,
|
||||||
|
YOMITAN_POPUP_MOUSE_LEAVE_EVENT,
|
||||||
YOMITAN_POPUP_SHOWN_EVENT,
|
YOMITAN_POPUP_SHOWN_EVENT,
|
||||||
YOMITAN_POPUP_VISIBLE_HOST_SELECTOR,
|
YOMITAN_POPUP_VISIBLE_HOST_SELECTOR,
|
||||||
} from '../yomitan-popup.js';
|
} from '../yomitan-popup.js';
|
||||||
@@ -89,6 +91,7 @@ function createMouseTestContext() {
|
|||||||
state: {
|
state: {
|
||||||
isOverSubtitle: false,
|
isOverSubtitle: false,
|
||||||
isOverSubtitleSidebar: false,
|
isOverSubtitleSidebar: false,
|
||||||
|
isOverYomitanPopup: false,
|
||||||
yomitanPopupVisible: false,
|
yomitanPopupVisible: false,
|
||||||
subtitleSidebarModalOpen: false,
|
subtitleSidebarModalOpen: false,
|
||||||
subtitleSidebarConfig: null as SubtitleSidebarConfig | null,
|
subtitleSidebarConfig: null as SubtitleSidebarConfig | null,
|
||||||
@@ -842,7 +845,12 @@ test('nested popup close reasserts interactive state and focus when another popu
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('window blur reclaims overlay focus while a yomitan popup remains visible on Windows', async () => {
|
function setupYomitanPopupFocusHarness(
|
||||||
|
options: {
|
||||||
|
isMacOSPlatform?: boolean;
|
||||||
|
visiblePopupHost?: boolean;
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
const ctx = createMouseTestContext();
|
const ctx = createMouseTestContext();
|
||||||
const previousWindow = (globalThis as { window?: unknown }).window;
|
const previousWindow = (globalThis as { window?: unknown }).window;
|
||||||
const previousDocument = (globalThis as { document?: unknown }).document;
|
const previousDocument = (globalThis as { document?: unknown }).document;
|
||||||
@@ -853,8 +861,10 @@ test('window blur reclaims overlay focus while a yomitan popup remains visible o
|
|||||||
let focusMainWindowCalls = 0;
|
let focusMainWindowCalls = 0;
|
||||||
let windowFocusCalls = 0;
|
let windowFocusCalls = 0;
|
||||||
let overlayFocusCalls = 0;
|
let overlayFocusCalls = 0;
|
||||||
|
let visiblePopupHostPresent = options.visiblePopupHost === true;
|
||||||
|
|
||||||
ctx.platform.shouldToggleMouseIgnore = true;
|
ctx.platform.shouldToggleMouseIgnore = true;
|
||||||
|
ctx.platform.isMacOSPlatform = options.isMacOSPlatform === true;
|
||||||
(ctx.dom.overlay as { focus?: (options?: { preventScroll?: boolean }) => void }).focus = () => {
|
(ctx.dom.overlay as { focus?: (options?: { preventScroll?: boolean }) => void }).focus = () => {
|
||||||
overlayFocusCalls += 1;
|
overlayFocusCalls += 1;
|
||||||
};
|
};
|
||||||
@@ -902,8 +912,8 @@ test('window blur reclaims overlay focus while a yomitan popup remains visible o
|
|||||||
querySelector: () => null,
|
querySelector: () => null,
|
||||||
querySelectorAll: (selector: string) => {
|
querySelectorAll: (selector: string) => {
|
||||||
if (
|
if (
|
||||||
selector === YOMITAN_POPUP_VISIBLE_HOST_SELECTOR ||
|
(visiblePopupHostPresent && selector === YOMITAN_POPUP_VISIBLE_HOST_SELECTOR) ||
|
||||||
selector === YOMITAN_POPUP_HOST_SELECTOR
|
(visiblePopupHostPresent && selector === YOMITAN_POPUP_HOST_SELECTOR)
|
||||||
) {
|
) {
|
||||||
return [visiblePopupHost];
|
return [visiblePopupHost];
|
||||||
}
|
}
|
||||||
@@ -927,7 +937,6 @@ test('window blur reclaims overlay focus while a yomitan popup remains visible o
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
|
||||||
const handlers = createMouseHandlers(ctx as never, {
|
const handlers = createMouseHandlers(ctx as never, {
|
||||||
modalStateReader: {
|
modalStateReader: {
|
||||||
isAnySettingsModalOpen: () => false,
|
isAnySettingsModalOpen: () => false,
|
||||||
@@ -941,32 +950,168 @@ test('window blur reclaims overlay focus while a yomitan popup remains visible o
|
|||||||
getPlaybackPaused: async () => false,
|
getPlaybackPaused: async () => false,
|
||||||
sendMpvCommand: () => {},
|
sendMpvCommand: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
handlers.setupYomitanObserver();
|
handlers.setupYomitanObserver();
|
||||||
assert.equal(ctx.state.yomitanPopupVisible, true);
|
|
||||||
assert.equal(ctx.dom.overlay.classList.contains('interactive'), true);
|
|
||||||
assert.deepEqual(ignoreCalls, [{ ignore: false, forward: undefined }]);
|
|
||||||
ignoreCalls.length = 0;
|
|
||||||
|
|
||||||
for (const listener of windowListeners.get('blur') ?? []) {
|
return {
|
||||||
listener();
|
ctx,
|
||||||
}
|
windowListeners,
|
||||||
await Promise.resolve();
|
ignoreCalls,
|
||||||
|
focusMainWindowCalls: () => focusMainWindowCalls,
|
||||||
assert.equal(ctx.state.yomitanPopupVisible, true);
|
windowFocusCalls: () => windowFocusCalls,
|
||||||
assert.equal(ctx.dom.overlay.classList.contains('interactive'), true);
|
overlayFocusCalls: () => overlayFocusCalls,
|
||||||
assert.deepEqual(ignoreCalls, [{ ignore: false, forward: undefined }]);
|
setVisiblePopupHost: (visible: boolean) => {
|
||||||
assert.equal(focusMainWindowCalls, 1);
|
visiblePopupHostPresent = visible;
|
||||||
assert.equal(windowFocusCalls, 1);
|
},
|
||||||
assert.equal(overlayFocusCalls, 1);
|
restore: () => {
|
||||||
} finally {
|
|
||||||
Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
|
Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
|
||||||
Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
|
Object.defineProperty(globalThis, 'document', {
|
||||||
|
configurable: true,
|
||||||
|
value: previousDocument,
|
||||||
|
});
|
||||||
Object.defineProperty(globalThis, 'MutationObserver', {
|
Object.defineProperty(globalThis, 'MutationObserver', {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
value: previousMutationObserver,
|
value: previousMutationObserver,
|
||||||
});
|
});
|
||||||
Object.defineProperty(globalThis, 'Node', { configurable: true, value: previousNode });
|
Object.defineProperty(globalThis, 'Node', { configurable: true, value: previousNode });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('window blur reclaims overlay focus while a yomitan popup remains visible on Windows', async () => {
|
||||||
|
const harness = setupYomitanPopupFocusHarness({ visiblePopupHost: true });
|
||||||
|
try {
|
||||||
|
assert.equal(harness.ctx.state.yomitanPopupVisible, true);
|
||||||
|
assert.equal(harness.ctx.dom.overlay.classList.contains('interactive'), true);
|
||||||
|
assert.deepEqual(harness.ignoreCalls, [{ ignore: false, forward: undefined }]);
|
||||||
|
harness.ignoreCalls.length = 0;
|
||||||
|
|
||||||
|
for (const listener of harness.windowListeners.get('blur') ?? []) {
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
assert.equal(harness.ctx.state.yomitanPopupVisible, true);
|
||||||
|
assert.equal(harness.ctx.dom.overlay.classList.contains('interactive'), true);
|
||||||
|
assert.deepEqual(harness.ignoreCalls, [{ ignore: false, forward: undefined }]);
|
||||||
|
assert.equal(harness.focusMainWindowCalls(), 1);
|
||||||
|
assert.equal(harness.windowFocusCalls(), 1);
|
||||||
|
assert.equal(harness.overlayFocusCalls(), 1);
|
||||||
|
} finally {
|
||||||
|
harness.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('window blur on macOS keeps yomitan popup interactive without stealing click-away focus', async () => {
|
||||||
|
const harness = setupYomitanPopupFocusHarness({
|
||||||
|
isMacOSPlatform: true,
|
||||||
|
visiblePopupHost: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
assert.equal(harness.ctx.state.yomitanPopupVisible, true);
|
||||||
|
assert.equal(harness.ctx.dom.overlay.classList.contains('interactive'), true);
|
||||||
|
assert.deepEqual(harness.ignoreCalls, [{ ignore: false, forward: undefined }]);
|
||||||
|
harness.ignoreCalls.length = 0;
|
||||||
|
|
||||||
|
for (const listener of harness.windowListeners.get('blur') ?? []) {
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
assert.equal(harness.ctx.state.yomitanPopupVisible, true);
|
||||||
|
assert.equal(harness.ctx.dom.overlay.classList.contains('interactive'), true);
|
||||||
|
assert.deepEqual(harness.ignoreCalls, [{ ignore: false, forward: undefined }]);
|
||||||
|
assert.equal(harness.focusMainWindowCalls(), 0);
|
||||||
|
assert.equal(harness.windowFocusCalls(), 0);
|
||||||
|
assert.equal(harness.overlayFocusCalls(), 0);
|
||||||
|
} finally {
|
||||||
|
harness.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('popup shown reclaims overlay focus on macOS and captures click-away', () => {
|
||||||
|
const harness = setupYomitanPopupFocusHarness({ isMacOSPlatform: true });
|
||||||
|
try {
|
||||||
|
harness.ignoreCalls.length = 0;
|
||||||
|
|
||||||
|
for (const listener of harness.windowListeners.get(YOMITAN_POPUP_SHOWN_EVENT) ?? []) {
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(harness.ctx.state.yomitanPopupVisible, true);
|
||||||
|
assert.equal(harness.ctx.dom.overlay.classList.contains('interactive'), true);
|
||||||
|
assert.deepEqual(harness.ignoreCalls, [{ ignore: false, forward: undefined }]);
|
||||||
|
assert.equal(harness.focusMainWindowCalls(), 1);
|
||||||
|
assert.equal(harness.windowFocusCalls(), 1);
|
||||||
|
assert.equal(harness.overlayFocusCalls(), 1);
|
||||||
|
} finally {
|
||||||
|
harness.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('popup mouse enter marks macOS Yomitan popup hover interactive', () => {
|
||||||
|
const harness = setupYomitanPopupFocusHarness({
|
||||||
|
isMacOSPlatform: true,
|
||||||
|
visiblePopupHost: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
harness.ignoreCalls.length = 0;
|
||||||
|
|
||||||
|
for (const listener of harness.windowListeners.get(YOMITAN_POPUP_MOUSE_ENTER_EVENT) ?? []) {
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(harness.ctx.state.yomitanPopupVisible, true);
|
||||||
|
assert.equal(harness.ctx.state.isOverYomitanPopup, true);
|
||||||
|
assert.equal(harness.ctx.dom.overlay.classList.contains('interactive'), true);
|
||||||
|
assert.deepEqual(harness.ignoreCalls, [{ ignore: false, forward: undefined }]);
|
||||||
|
} finally {
|
||||||
|
harness.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('popup mouse leave on macOS keeps click-away captured while popup remains visible', () => {
|
||||||
|
const harness = setupYomitanPopupFocusHarness({
|
||||||
|
isMacOSPlatform: true,
|
||||||
|
visiblePopupHost: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
for (const listener of harness.windowListeners.get(YOMITAN_POPUP_MOUSE_ENTER_EVENT) ?? []) {
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
harness.ignoreCalls.length = 0;
|
||||||
|
|
||||||
|
for (const listener of harness.windowListeners.get(YOMITAN_POPUP_MOUSE_LEAVE_EVENT) ?? []) {
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(harness.ctx.state.yomitanPopupVisible, true);
|
||||||
|
assert.equal(harness.ctx.state.isOverYomitanPopup, false);
|
||||||
|
assert.equal(harness.ctx.dom.overlay.classList.contains('interactive'), true);
|
||||||
|
assert.deepEqual(harness.ignoreCalls, [{ ignore: false, forward: undefined }]);
|
||||||
|
} finally {
|
||||||
|
harness.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('popup hidden on macOS releases click-away capture back to mpv', () => {
|
||||||
|
const harness = setupYomitanPopupFocusHarness({
|
||||||
|
isMacOSPlatform: true,
|
||||||
|
visiblePopupHost: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
assert.equal(harness.ctx.dom.overlay.classList.contains('interactive'), true);
|
||||||
|
harness.ignoreCalls.length = 0;
|
||||||
|
harness.setVisiblePopupHost(false);
|
||||||
|
|
||||||
|
for (const listener of harness.windowListeners.get(YOMITAN_POPUP_HIDDEN_EVENT) ?? []) {
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(harness.ctx.state.yomitanPopupVisible, false);
|
||||||
|
assert.equal(harness.ctx.dom.overlay.classList.contains('interactive'), false);
|
||||||
|
assert.deepEqual(harness.ignoreCalls.at(-1), { ignore: true, forward: true });
|
||||||
|
} finally {
|
||||||
|
harness.restore();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export function createMouseHandlers(
|
|||||||
if (!ctx.platform.shouldToggleMouseIgnore) {
|
if (!ctx.platform.shouldToggleMouseIgnore) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ctx.platform.isMacOSPlatform || ctx.platform.isLinuxPlatform) {
|
if (ctx.platform.isLinuxPlatform) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,6 +305,7 @@ export function createMouseHandlers(
|
|||||||
|
|
||||||
yomitanPopupVisible = false;
|
yomitanPopupVisible = false;
|
||||||
ctx.state.yomitanPopupVisible = false;
|
ctx.state.yomitanPopupVisible = false;
|
||||||
|
ctx.state.isOverYomitanPopup = false;
|
||||||
syncPrimaryVisibleOnYomitanPopupClass(false);
|
syncPrimaryVisibleOnYomitanPopupClass(false);
|
||||||
popupPauseRequestId += 1;
|
popupPauseRequestId += 1;
|
||||||
maybeResumeYomitanPopupPause();
|
maybeResumeYomitanPopupPause();
|
||||||
@@ -378,7 +379,10 @@ export function createMouseHandlers(
|
|||||||
}
|
}
|
||||||
hoverPauseRequestId += 1;
|
hoverPauseRequestId += 1;
|
||||||
maybeResumeHoverPause();
|
maybeResumeHoverPause();
|
||||||
if (yomitanPopupVisible) return;
|
if (yomitanPopupVisible) {
|
||||||
|
syncOverlayMouseIgnoreState(ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
disablePopupInteractionIfIdle();
|
disablePopupInteractionIfIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,7 +471,11 @@ export function createMouseHandlers(
|
|||||||
reconcilePopupInteraction({ allowPause: true });
|
reconcilePopupInteraction({ allowPause: true });
|
||||||
|
|
||||||
window.addEventListener(YOMITAN_POPUP_SHOWN_EVENT, () => {
|
window.addEventListener(YOMITAN_POPUP_SHOWN_EVENT, () => {
|
||||||
reconcilePopupInteraction({ assumeVisible: true, allowPause: true });
|
reconcilePopupInteraction({
|
||||||
|
assumeVisible: true,
|
||||||
|
allowPause: true,
|
||||||
|
reclaimFocus: ctx.platform.isMacOSPlatform,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener(YOMITAN_POPUP_HIDDEN_EVENT, () => {
|
window.addEventListener(YOMITAN_POPUP_HIDDEN_EVENT, () => {
|
||||||
@@ -475,10 +483,12 @@ export function createMouseHandlers(
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener(YOMITAN_POPUP_MOUSE_ENTER_EVENT, () => {
|
window.addEventListener(YOMITAN_POPUP_MOUSE_ENTER_EVENT, () => {
|
||||||
|
ctx.state.isOverYomitanPopup = true;
|
||||||
reconcilePopupInteraction({ assumeVisible: true, reclaimFocus: true });
|
reconcilePopupInteraction({ assumeVisible: true, reclaimFocus: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener(YOMITAN_POPUP_MOUSE_LEAVE_EVENT, () => {
|
window.addEventListener(YOMITAN_POPUP_MOUSE_LEAVE_EVENT, () => {
|
||||||
|
ctx.state.isOverYomitanPopup = false;
|
||||||
reconcilePopupInteraction({ assumeVisible: true });
|
reconcilePopupInteraction({ assumeVisible: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -491,7 +501,7 @@ export function createMouseHandlers(
|
|||||||
if (typeof document === 'undefined' || document.visibilityState !== 'visible') {
|
if (typeof document === 'undefined' || document.visibilityState !== 'visible') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
reconcilePopupInteraction({ reclaimFocus: true });
|
reconcilePopupInteraction({ reclaimFocus: !ctx.platform.isMacOSPlatform });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -176,6 +176,111 @@ test('visible yomitan popup host keeps overlay interactive even when cached popu
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('visible yomitan popup host on macOS keeps overlay interactive so click-away reaches popup', () => {
|
||||||
|
const classList = createClassList();
|
||||||
|
const ignoreCalls: Array<{ ignore: boolean; forward?: boolean }> = [];
|
||||||
|
|
||||||
|
const restoreWindow = replaceGlobalProperty('window', {
|
||||||
|
electronAPI: {
|
||||||
|
setIgnoreMouseEvents: (ignore: boolean, options?: { forward?: boolean }) => {
|
||||||
|
ignoreCalls.push({ ignore, forward: options?.forward });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getComputedStyle: () => ({
|
||||||
|
visibility: 'visible',
|
||||||
|
display: 'block',
|
||||||
|
opacity: '1',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const restoreDocument = replaceGlobalProperty('document', {
|
||||||
|
querySelectorAll: (selector: string) =>
|
||||||
|
selector ===
|
||||||
|
'[data-subminer-yomitan-popup-host="true"][data-subminer-yomitan-popup-visible="true"]'
|
||||||
|
? [{ getAttribute: () => 'true' }]
|
||||||
|
: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
syncOverlayMouseIgnoreState({
|
||||||
|
dom: {
|
||||||
|
overlay: { classList },
|
||||||
|
},
|
||||||
|
platform: {
|
||||||
|
isMacOSPlatform: true,
|
||||||
|
shouldToggleMouseIgnore: true,
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
isOverSubtitle: false,
|
||||||
|
isOverSubtitleSidebar: false,
|
||||||
|
isOverYomitanPopup: false,
|
||||||
|
yomitanPopupVisible: false,
|
||||||
|
controllerSelectModalOpen: false,
|
||||||
|
controllerDebugModalOpen: false,
|
||||||
|
jimakuModalOpen: false,
|
||||||
|
youtubePickerModalOpen: false,
|
||||||
|
kikuModalOpen: false,
|
||||||
|
runtimeOptionsModalOpen: false,
|
||||||
|
subsyncModalOpen: false,
|
||||||
|
sessionHelpModalOpen: false,
|
||||||
|
subtitleSidebarModalOpen: false,
|
||||||
|
subtitleSidebarConfig: null,
|
||||||
|
},
|
||||||
|
} as never);
|
||||||
|
|
||||||
|
assert.equal(classList.contains('interactive'), true);
|
||||||
|
assert.deepEqual(ignoreCalls, [{ ignore: false, forward: undefined }]);
|
||||||
|
} finally {
|
||||||
|
restoreDocument();
|
||||||
|
restoreWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('macOS pointer over a visible yomitan popup keeps the overlay interactive', () => {
|
||||||
|
const classList = createClassList();
|
||||||
|
const ignoreCalls: Array<{ ignore: boolean; forward?: boolean }> = [];
|
||||||
|
|
||||||
|
const restoreWindow = replaceGlobalProperty('window', {
|
||||||
|
electronAPI: {
|
||||||
|
setIgnoreMouseEvents: (ignore: boolean, options?: { forward?: boolean }) => {
|
||||||
|
ignoreCalls.push({ ignore, forward: options?.forward });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
syncOverlayMouseIgnoreState({
|
||||||
|
dom: {
|
||||||
|
overlay: { classList },
|
||||||
|
},
|
||||||
|
platform: {
|
||||||
|
isMacOSPlatform: true,
|
||||||
|
shouldToggleMouseIgnore: true,
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
isOverSubtitle: false,
|
||||||
|
isOverSubtitleSidebar: false,
|
||||||
|
isOverYomitanPopup: true,
|
||||||
|
yomitanPopupVisible: true,
|
||||||
|
controllerSelectModalOpen: false,
|
||||||
|
controllerDebugModalOpen: false,
|
||||||
|
jimakuModalOpen: false,
|
||||||
|
youtubePickerModalOpen: false,
|
||||||
|
kikuModalOpen: false,
|
||||||
|
runtimeOptionsModalOpen: false,
|
||||||
|
subsyncModalOpen: false,
|
||||||
|
sessionHelpModalOpen: false,
|
||||||
|
subtitleSidebarModalOpen: false,
|
||||||
|
subtitleSidebarConfig: null,
|
||||||
|
},
|
||||||
|
} as never);
|
||||||
|
|
||||||
|
assert.equal(classList.contains('interactive'), true);
|
||||||
|
assert.deepEqual(ignoreCalls, [{ ignore: false, forward: undefined }]);
|
||||||
|
} finally {
|
||||||
|
restoreWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('Linux subtitle hover keeps root passive and does not report whole-window interactive hint', () => {
|
test('Linux subtitle hover keeps root passive and does not report whole-window interactive hint', () => {
|
||||||
const classList = createClassList();
|
const classList = createClassList();
|
||||||
const interactiveHints: boolean[] = [];
|
const interactiveHints: boolean[] = [];
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export function syncOverlayMouseIgnoreState(ctx: RendererContext): void {
|
|||||||
const shouldStayInteractive =
|
const shouldStayInteractive =
|
||||||
ctx.state.isOverSubtitle ||
|
ctx.state.isOverSubtitle ||
|
||||||
ctx.state.isOverSubtitleSidebar ||
|
ctx.state.isOverSubtitleSidebar ||
|
||||||
|
ctx.state.isOverYomitanPopup ||
|
||||||
ctx.state.isOverOverlayNotification ||
|
ctx.state.isOverOverlayNotification ||
|
||||||
ctx.state.isOverNotificationHistory ||
|
ctx.state.isOverNotificationHistory ||
|
||||||
shouldKeepWindowInteractive;
|
shouldKeepWindowInteractive;
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ export type RendererState = {
|
|||||||
keyboardSelectionVisible: boolean;
|
keyboardSelectionVisible: boolean;
|
||||||
keyboardSelectedWordIndex: number | null;
|
keyboardSelectedWordIndex: number | null;
|
||||||
yomitanPopupVisible: boolean;
|
yomitanPopupVisible: boolean;
|
||||||
|
isOverYomitanPopup: boolean;
|
||||||
primarySubtitleMode: PrimarySubMode;
|
primarySubtitleMode: PrimarySubMode;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -254,6 +255,7 @@ export function createRendererState(): RendererState {
|
|||||||
keyboardSelectionVisible: false,
|
keyboardSelectionVisible: false,
|
||||||
keyboardSelectedWordIndex: null,
|
keyboardSelectedWordIndex: null,
|
||||||
yomitanPopupVisible: false,
|
yomitanPopupVisible: false,
|
||||||
|
isOverYomitanPopup: false,
|
||||||
primarySubtitleMode: 'visible',
|
primarySubtitleMode: 'visible',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user