fix(overlay): keep macOS modal windows on fullscreen Spaces (#200)

This commit is contained in:
2026-08-15 21:43:26 -07:00
committed by GitHub
parent 2174e689a2
commit a02c33dac4
22 changed files with 568 additions and 98 deletions
+294 -63
View File
@@ -16,6 +16,7 @@ type MockWindow = {
loading: boolean;
url: string;
contentReady: boolean;
documentLoaded: boolean;
loadCallbacks: Array<() => void>;
readyToShowCallbacks: Array<() => void>;
};
@@ -31,6 +32,7 @@ function createMockWindow(): MockWindow & {
getShowCount: () => number;
getHideCount: () => number;
show: () => void;
showInactive: () => void;
hide: () => void;
destroy: () => void;
focus: () => void;
@@ -61,6 +63,7 @@ function createMockWindow(): MockWindow & {
loading: false,
url: 'file:///overlay/index.html?layer=modal',
contentReady: true,
documentLoaded: true,
loadCallbacks: [],
readyToShowCallbacks: [],
};
@@ -84,6 +87,10 @@ function createMockWindow(): MockWindow & {
state.visible = true;
state.showCount += 1;
},
showInactive: () => {
state.visible = true;
state.showCount += 1;
},
hide: () => {
state.visible = false;
state.hideCount += 1;
@@ -96,6 +103,10 @@ function createMockWindow(): MockWindow & {
state.focused = true;
},
emitDidFinishLoad: () => {
state.documentLoaded = true;
(
window as typeof window & { __subminerOverlayDocumentLoaded?: boolean }
).__subminerOverlayDocumentLoaded = true;
const callbacks = state.loadCallbacks.splice(0);
for (const callback of callbacks) {
callback();
@@ -197,9 +208,22 @@ function createMockWindow(): MockWindow & {
},
});
Object.defineProperty(window, 'documentLoaded', {
get: () => state.documentLoaded,
set: (value: boolean) => {
state.documentLoaded = value;
(
window as typeof window & { __subminerOverlayDocumentLoaded?: boolean }
).__subminerOverlayDocumentLoaded = value;
},
});
(
window as typeof window & { __subminerOverlayContentReady?: boolean }
).__subminerOverlayContentReady = state.contentReady;
(
window as typeof window & { __subminerOverlayDocumentLoaded?: boolean }
).__subminerOverlayDocumentLoaded = state.documentLoaded;
return window;
}
@@ -259,6 +283,73 @@ test('sendToActiveOverlayWindow creates modal window lazily when absent', () =>
assert.deepEqual(window.sent, [['jimaku:open']]);
});
for (const platform of ['darwin', 'win32'] as const) {
test(`primeModalWindow creates and warms a hidden modal on ${platform}`, () => {
const modalWindow = createMockWindow();
modalWindow.loading = true;
modalWindow.url = '';
modalWindow.contentReady = false;
modalWindow.documentLoaded = false;
let currentModal: ReturnType<typeof createMockWindow> | null = null;
let createCalls = 0;
const runtime = createOverlayModalRuntimeService(
{
getMainWindow: () => null,
getModalWindow: () => currentModal as never,
createModalWindow: () => {
createCalls += 1;
currentModal = modalWindow;
return modalWindow as never;
},
getModalGeometry: () => ({ x: 1, y: 2, width: 300, height: 200 }),
setModalWindowBounds: () => {},
},
{ platform },
);
assert.equal(runtime.primeModalWindow(), true);
assert.equal(createCalls, 1);
assert.equal(modalWindow.isVisible(), false);
modalWindow.loading = false;
modalWindow.url = 'file:///overlay/index.html?layer=modal';
modalWindow.emitDidFinishLoad();
modalWindow.emitReadyToShow();
modalWindow.contentReady = true;
assert.equal(
runtime.sendToActiveOverlayWindow('session-help:open', undefined, {
restoreOnModalClose: 'session-help',
preferModalWindow: true,
}),
true,
);
assert.equal(createCalls, 1);
assert.equal(modalWindow.isVisible(), true);
assert.deepEqual(modalWindow.sent, [['session-help:open']]);
});
}
test('primeModalWindow leaves Linux modal creation lazy', () => {
let createCalls = 0;
const runtime = createOverlayModalRuntimeService(
{
getMainWindow: () => null,
getModalWindow: () => null,
createModalWindow: () => {
createCalls += 1;
return createMockWindow() as never;
},
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
setModalWindowBounds: () => {},
},
{ platform: 'linux' },
);
assert.equal(runtime.primeModalWindow(), false);
assert.equal(createCalls, 0);
});
test('sendToActiveOverlayWindow does not retain restore state when modal creation fails', () => {
const runtime = createOverlayModalRuntimeService({
getMainWindow: () => null,
@@ -301,7 +392,7 @@ test('sendToActiveOverlayWindow waits for blank modal URL before sending open co
window.loading = false;
window.url = 'file:///overlay/index.html?layer=modal';
window.emitDidFinishLoad();
assert.deepEqual(window.sent, []);
assert.deepEqual(window.sent, [['runtime-options:open']]);
window.contentReady = true;
window.emitReadyToShow();
@@ -311,15 +402,18 @@ test('sendToActiveOverlayWindow waits for blank modal URL before sending open co
assert.equal(window.getShowCount(), 1);
});
test('handleOverlayModalClosed hides modal window only after all pending modals close', () => {
test('handleOverlayModalClosed keeps the modal window warm after all pending modals close', () => {
const window = createMockWindow();
const runtime = createOverlayModalRuntimeService({
getMainWindow: () => null,
getModalWindow: () => window as never,
createModalWindow: () => window as never,
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
setModalWindowBounds: () => {},
});
const runtime = createOverlayModalRuntimeService(
{
getMainWindow: () => null,
getModalWindow: () => window as never,
createModalWindow: () => window as never,
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
setModalWindowBounds: () => {},
},
{ platform: 'darwin' },
);
runtime.sendToActiveOverlayWindow('runtime-options:open', undefined, {
restoreOnModalClose: 'runtime-options',
@@ -342,7 +436,9 @@ test('handleOverlayModalClosed hides modal window only after all pending modals
assert.equal(window.isDestroyed(), false);
runtime.handleOverlayModalClosed('subsync');
assert.equal(window.isDestroyed(), true);
assert.equal(window.isDestroyed(), false);
assert.equal(window.isVisible(), false);
assert.equal(window.ignoreMouseEvents, true);
});
test('sendToActiveOverlayWindow prefers visible main overlay window for modal open', () => {
@@ -464,6 +560,46 @@ test('modal window path restores visible main overlay before modal input deactiv
assert.deepEqual(events, ['state:true:visible:true', 'state:false:visible:true']);
});
test('macOS maps a new modal panel before focusing SubMiner and hiding the subtitle overlay', () => {
const mainWindow = createMockWindow();
mainWindow.visible = true;
const modalWindow = createMockWindow();
const events: string[] = [];
const showInactive = modalWindow.showInactive;
modalWindow.showInactive = () => {
events.push('show-inactive');
showInactive();
};
const hideMainWindow = mainWindow.hide;
mainWindow.hide = () => {
events.push('hide-main');
hideMainWindow();
};
const runtime = createOverlayModalRuntimeService(
{
getMainWindow: () => mainWindow as never,
getModalWindow: () => modalWindow as never,
createModalWindow: () => modalWindow as never,
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
setModalWindowBounds: () => {},
},
{
platform: 'darwin',
focusApplication: () => events.push('focus-application'),
},
);
runtime.sendToActiveOverlayWindow('runtime-options:open', undefined, {
restoreOnModalClose: 'runtime-options',
preferModalWindow: true,
});
runtime.notifyOverlayModalOpened('runtime-options');
assert.deepEqual(events, ['show-inactive', 'focus-application', 'hide-main']);
assert.equal(modalWindow.isVisible(), true);
assert.equal(mainWindow.isVisible(), false);
});
test('modal window path runs final close handoff before modal input deactivates', () => {
const mainWindow = createMockWindow();
mainWindow.visible = true;
@@ -650,15 +786,18 @@ test('handleOverlayModalClosed is a no-op when no modal window can be targeted',
assert.deepEqual(state, []);
});
test('handleOverlayModalClosed destroys modal window for single kiku modal', () => {
test('handleOverlayModalClosed hides and retains modal window for single kiku modal', () => {
const window = createMockWindow();
const runtime = createOverlayModalRuntimeService({
getMainWindow: () => null,
getModalWindow: () => window as never,
createModalWindow: () => window as never,
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
setModalWindowBounds: () => {},
});
const runtime = createOverlayModalRuntimeService(
{
getMainWindow: () => null,
getModalWindow: () => window as never,
createModalWindow: () => window as never,
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
setModalWindowBounds: () => {},
},
{ platform: 'darwin' },
);
runtime.sendToActiveOverlayWindow(
'kiku:field-grouping-open',
@@ -669,7 +808,9 @@ test('handleOverlayModalClosed destroys modal window for single kiku modal', ()
);
runtime.handleOverlayModalClosed('kiku');
assert.equal(window.isDestroyed(), true);
assert.equal(window.isDestroyed(), false);
assert.equal(window.isVisible(), false);
assert.equal(window.ignoreMouseEvents, true);
assert.equal(runtime.getRestoreVisibleOverlayOnModalClose().size, 0);
});
@@ -719,8 +860,10 @@ test('modal fallback reveal skips showing window when content is not ready', asy
assert.equal(window.ignoreMouseEvents, false);
});
test('sendToActiveOverlayWindow waits for modal ready-to-show before delivering open event', () => {
test('sendToActiveOverlayWindow delivers on first modal load without waiting for ready-to-show', () => {
const window = createMockWindow();
window.loading = true;
window.url = '';
window.contentReady = false;
const runtime = createOverlayModalRuntimeService({
getMainWindow: () => null,
@@ -738,16 +881,100 @@ test('sendToActiveOverlayWindow waits for modal ready-to-show before delivering
assert.equal(sent, true);
assert.deepEqual(window.sent, []);
window.loading = false;
window.url = 'file:///overlay/index.html?layer=modal';
window.emitDidFinishLoad();
assert.deepEqual(window.sent, []);
assert.deepEqual(window.sent, [['runtime-options:open']]);
window.contentReady = true;
window.emitReadyToShow();
assert.deepEqual(window.sent, [['runtime-options:open']]);
});
test('sendToActiveOverlayWindow delivers when the modal loaded before listeners were registered', () => {
const window = createMockWindow();
window.contentReady = false;
const runtime = createOverlayModalRuntimeService({
getMainWindow: () => null,
getModalWindow: () => window as never,
createModalWindow: () => {
throw new Error('modal window should not be created when already present');
},
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
setModalWindowBounds: () => {},
});
assert.equal(
runtime.sendToActiveOverlayWindow('runtime-options:open', undefined, {
restoreOnModalClose: 'runtime-options',
}),
true,
);
assert.deepEqual(window.sent, [['runtime-options:open']]);
window.contentReady = true;
window.emitReadyToShow();
assert.deepEqual(window.sent, [['runtime-options:open']]);
});
test('sendToActiveOverlayWindow does not infer document readiness from a pending file URL', () => {
const window = createMockWindow();
window.contentReady = false;
window.documentLoaded = false;
window.loading = false;
const runtime = createOverlayModalRuntimeService({
getMainWindow: () => null,
getModalWindow: () => window as never,
createModalWindow: () => {
throw new Error('modal window should not be created when already present');
},
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
setModalWindowBounds: () => {},
});
assert.equal(
runtime.sendToActiveOverlayWindow('runtime-options:open', undefined, {
restoreOnModalClose: 'runtime-options',
}),
true,
);
assert.deepEqual(window.sent, []);
window.emitDidFinishLoad();
assert.deepEqual(window.sent, [['runtime-options:open']]);
});
test('sendToActiveOverlayWindow rejects stale content readiness during document reload', () => {
const window = createMockWindow();
window.contentReady = true;
window.documentLoaded = false;
window.loading = false;
const runtime = createOverlayModalRuntimeService({
getMainWindow: () => null,
getModalWindow: () => window as never,
createModalWindow: () => {
throw new Error('modal window should not be created when already present');
},
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
setModalWindowBounds: () => {},
});
assert.equal(
runtime.sendToActiveOverlayWindow('session-help:open', undefined, {
restoreOnModalClose: 'session-help',
}),
true,
);
assert.deepEqual(window.sent, []);
window.emitDidFinishLoad();
assert.deepEqual(window.sent, [['session-help:open']]);
});
test('sendToActiveOverlayWindow flushes every queued load and ready listener before sending', () => {
const window = createMockWindow();
window.loading = true;
window.url = '';
window.contentReady = false;
const runtime = createOverlayModalRuntimeService({
getMainWindow: () => null,
@@ -773,61 +1000,64 @@ test('sendToActiveOverlayWindow flushes every queued load and ready listener bef
);
assert.deepEqual(window.sent, []);
window.loading = false;
window.url = 'file:///overlay/index.html?layer=modal';
window.emitDidFinishLoad();
assert.deepEqual(window.sent, []);
assert.deepEqual(window.sent, [['runtime-options:open'], ['session-help:open']]);
window.contentReady = true;
window.emitReadyToShow();
assert.deepEqual(window.sent, [['runtime-options:open'], ['session-help:open']]);
});
test('modal reopen creates a fresh window after close destroys the previous one', () => {
const firstWindow = createMockWindow();
const secondWindow = createMockWindow();
let currentModal: ReturnType<typeof createMockWindow> | null = firstWindow;
for (const platform of ['darwin', 'win32'] as const) {
test(`modal reopen reuses the warm window and shows it immediately on ${platform}`, () => {
const modalWindow = createMockWindow();
let createCalls = 0;
const runtime = createOverlayModalRuntimeService({
getMainWindow: () => null,
getModalWindow: () => currentModal as never,
createModalWindow: () => {
currentModal = secondWindow;
return secondWindow as never;
},
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
setModalWindowBounds: () => {},
const runtime = createOverlayModalRuntimeService(
{
getMainWindow: () => null,
getModalWindow: () => modalWindow as never,
createModalWindow: () => {
createCalls += 1;
return modalWindow as never;
},
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
setModalWindowBounds: () => {},
},
{ platform },
);
runtime.sendToActiveOverlayWindow('runtime-options:open', undefined, {
restoreOnModalClose: 'runtime-options',
});
runtime.notifyOverlayModalOpened('runtime-options');
runtime.handleOverlayModalClosed('runtime-options');
assert.equal(modalWindow.isDestroyed(), false);
assert.equal(modalWindow.isVisible(), false);
const sent = runtime.sendToActiveOverlayWindow('runtime-options:open', undefined, {
restoreOnModalClose: 'runtime-options',
});
assert.equal(sent, true);
assert.equal(createCalls, 0);
assert.equal(modalWindow.isVisible(), true);
assert.equal(modalWindow.getShowCount(), 2);
});
}
runtime.sendToActiveOverlayWindow('runtime-options:open', undefined, {
restoreOnModalClose: 'runtime-options',
});
runtime.notifyOverlayModalOpened('runtime-options');
runtime.handleOverlayModalClosed('runtime-options');
assert.equal(firstWindow.isDestroyed(), true);
const sent = runtime.sendToActiveOverlayWindow('runtime-options:open', undefined, {
restoreOnModalClose: 'runtime-options',
});
assert.equal(sent, true);
assert.equal(currentModal, secondWindow);
assert.equal(secondWindow.getShowCount(), 0);
});
test('modal reopen after close-destroy notifies state change on fresh window lifecycle', () => {
const firstWindow = createMockWindow();
const secondWindow = createMockWindow();
let currentModal: ReturnType<typeof createMockWindow> | null = firstWindow;
test('modal reopen on the warm window notifies state change for each lifecycle', () => {
const modalWindow = createMockWindow();
const state: boolean[] = [];
const runtime = createOverlayModalRuntimeService(
{
getMainWindow: () => null,
getModalWindow: () => currentModal as never,
createModalWindow: () => {
currentModal = secondWindow;
return secondWindow as never;
},
getModalWindow: () => modalWindow as never,
createModalWindow: () => modalWindow as never,
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
setModalWindowBounds: () => {},
},
@@ -835,6 +1065,7 @@ test('modal reopen after close-destroy notifies state change on fresh window lif
onModalStateChange: (active: boolean): void => {
state.push(active);
},
platform: 'darwin',
},
);
@@ -845,7 +1076,7 @@ test('modal reopen after close-destroy notifies state change on fresh window lif
runtime.handleOverlayModalClosed('runtime-options');
assert.deepEqual(state, [true, false]);
assert.equal(firstWindow.isDestroyed(), true);
assert.equal(modalWindow.isDestroyed(), false);
runtime.sendToActiveOverlayWindow('runtime-options:open', undefined, {
restoreOnModalClose: 'runtime-options',
@@ -853,7 +1084,7 @@ test('modal reopen after close-destroy notifies state change on fresh window lif
runtime.notifyOverlayModalOpened('runtime-options');
assert.deepEqual(state, [true, false, true]);
assert.equal(currentModal, secondWindow);
assert.equal(modalWindow.isVisible(), true);
});
test('visible stale modal window is made interactive again before reopening', () => {
+91 -17
View File
@@ -2,7 +2,10 @@ import type { BrowserWindow } from 'electron';
import type { OverlayHostedModal } from '../shared/ipc/contracts';
import type { WindowGeometry } from '../types';
import type { HyprlandPlacementStatus } from '../core/services/hyprland-window-placement';
import { OVERLAY_WINDOW_CONTENT_READY_FLAG } from '../core/services/overlay-window-flags';
import {
OVERLAY_WINDOW_CONTENT_READY_FLAG,
OVERLAY_WINDOW_DOCUMENT_LOADED_FLAG,
} from '../core/services/overlay-window-flags';
const MODAL_REVEAL_FALLBACK_DELAY_MS = 250;
// The dedicated modal window maps asynchronously on Wayland; a single reconcile can fire
@@ -39,6 +42,7 @@ export interface OverlayWindowResolver {
}
export interface OverlayModalRuntime {
primeModalWindow: () => boolean;
sendToActiveOverlayWindow: (
channel: string,
payload?: unknown,
@@ -59,6 +63,8 @@ export interface OverlayModalRuntime {
type RevealFallbackHandle = NonNullable<Parameters<typeof globalThis.clearTimeout>[0]>;
export interface OverlayModalRuntimeOptions {
platform?: NodeJS.Platform;
focusApplication?: () => void;
onModalStateChange?: (isActive: boolean) => void;
onFinalModalClosed?: () => void;
scheduleRevealFallback?: (callback: () => void, delayMs: number) => RevealFallbackHandle;
@@ -79,6 +85,10 @@ export function createOverlayModalRuntimeService(
let pendingModalWindowReveal: BrowserWindow | null = null;
let pendingModalWindowRevealTimeout: RevealFallbackHandle | null = null;
const modalWindowBoundsReconcileGenerations = new WeakMap<BrowserWindow, number>();
const modalWindowPrimeListenersRegistered = new WeakSet<BrowserWindow>();
const platform = options.platform ?? process.platform;
const keepModalWindowWarm = platform === 'darwin' || platform === 'win32';
const focusApplication = options.focusApplication ?? requestOverlayApplicationFocus;
const scheduleRevealFallback = (callback: () => void, delayMs: number): RevealFallbackHandle =>
(options.scheduleRevealFallback ?? globalThis.setTimeout)(callback, delayMs);
const clearRevealFallback = (timeout: RevealFallbackHandle): void =>
@@ -134,7 +144,11 @@ export function createOverlayModalRuntimeService(
}
const overlayWindow = window as BrowserWindow & {
[OVERLAY_WINDOW_CONTENT_READY_FLAG]?: boolean;
[OVERLAY_WINDOW_DOCUMENT_LOADED_FLAG]?: boolean;
};
if (overlayWindow[OVERLAY_WINDOW_DOCUMENT_LOADED_FLAG] === false) {
return false;
}
if (
typeof overlayWindow[OVERLAY_WINDOW_CONTENT_READY_FLAG] === 'boolean' &&
overlayWindow[OVERLAY_WINDOW_CONTENT_READY_FLAG] !== true
@@ -145,6 +159,50 @@ export function createOverlayModalRuntimeService(
return currentURL !== '' && currentURL !== 'about:blank';
};
const isWindowLoadedForIpc = (window: BrowserWindow): boolean => {
if (window.isDestroyed() || window.webContents.isLoading()) {
return false;
}
const overlayWindow = window as BrowserWindow & {
[OVERLAY_WINDOW_DOCUMENT_LOADED_FLAG]?: boolean;
};
if (overlayWindow[OVERLAY_WINDOW_DOCUMENT_LOADED_FLAG] !== true) {
return false;
}
const currentURL = window.webContents.getURL();
return currentURL !== '' && currentURL !== 'about:blank';
};
const markModalWindowPrimed = (window: BrowserWindow): void => {
if (deps.getModalWindow() !== window || !isWindowLoadedForIpc(window)) {
return;
}
modalWindowPrimedForImmediateShow = true;
};
const primeModalWindow = (): boolean => {
if (!keepModalWindowWarm) {
return false;
}
const modalWindow = resolveModalWindow();
if (!modalWindow) {
return false;
}
deps.setModalWindowBounds(deps.getModalGeometry());
if (isWindowReadyForIpc(modalWindow)) {
modalWindowPrimedForImmediateShow = true;
return true;
}
if (!modalWindowPrimeListenersRegistered.has(modalWindow)) {
modalWindowPrimeListenersRegistered.add(modalWindow);
modalWindow.webContents.once('did-finish-load', () => markModalWindowPrimed(modalWindow));
modalWindow.once('ready-to-show', () => markModalWindowPrimed(modalWindow));
}
return true;
};
const elevateModalWindow = (window: BrowserWindow): void => {
if (window.isDestroyed()) return;
window.setAlwaysOnTop(true, 'screen-saver', 3);
@@ -205,16 +263,19 @@ export function createOverlayModalRuntimeService(
}
let delivered = false;
const deliverWhenReady = (): void => {
if (delivered || window.isDestroyed() || !isWindowReadyForIpc(window)) {
const deliver = (isReady: () => boolean): void => {
if (delivered || window.isDestroyed() || !isReady()) {
return;
}
delivered = true;
sendNow(window);
};
window.webContents.once('did-finish-load', deliverWhenReady);
window.once('ready-to-show', deliverWhenReady);
// A hidden macOS panel may not emit ready-to-show until it is presented. The
// renderer can safely receive IPC as soon as its document has finished loading.
window.webContents.once('did-finish-load', () => deliver(() => isWindowLoadedForIpc(window)));
window.once('ready-to-show', () => deliver(() => isWindowReadyForIpc(window)));
deliver(() => isWindowLoadedForIpc(window));
};
const showModalWindow = (
@@ -224,8 +285,15 @@ export function createOverlayModalRuntimeService(
} = { passThroughMouseEvents: false },
): void => {
setWindowFocusable(window);
requestOverlayApplicationFocus();
if (!window.isVisible()) {
const wasVisible = window.isVisible();
if (!wasVisible && platform === 'darwin') {
// Mapping the panel first keeps it attached to mpv's active fullscreen Space.
window.showInactive();
focusApplication();
} else {
focusApplication();
}
if (!wasVisible && platform !== 'darwin') {
window.show();
}
elevateModalWindow(window);
@@ -245,11 +313,11 @@ export function createOverlayModalRuntimeService(
const ensureModalWindowInteractive = (window: BrowserWindow): void => {
setWindowFocusable(window);
requestOverlayApplicationFocus();
window.setIgnoreMouseEvents(false);
elevateModalWindow(window);
if (window.isVisible()) {
focusApplication();
window.focus();
window.webContents.focus();
const reconcileGeneration = nextModalWindowBoundsReconcileGeneration(window);
@@ -447,9 +515,15 @@ export function createOverlayModalRuntimeService(
if (restoreVisibleOverlayOnModalClose.size === 0) {
clearPendingModalWindowReveal();
if (modalWindow && !modalWindow.isDestroyed()) {
modalWindow.destroy();
if (keepModalWindowWarm) {
modalWindow.setIgnoreMouseEvents(true, { forward: true });
modalWindow.hide();
markModalWindowPrimed(modalWindow);
} else {
modalWindow.destroy();
modalWindowPrimedForImmediateShow = false;
}
}
modalWindowPrimedForImmediateShow = false;
mainWindowMousePassthroughForcedByModal = false;
setMainWindowVisibilityForModal(false);
try {
@@ -478,17 +552,16 @@ export function createOverlayModalRuntimeService(
}
const modalWindow = deps.getModalWindow();
if (targetWindow.isVisible()) {
ensureModalWindowInteractive(targetWindow);
} else {
showModalWindow(targetWindow);
}
if (modalWindow && !modalWindow.isDestroyed() && targetWindow === modalWindow) {
setMainWindowMousePassthroughForModal(true);
setMainWindowVisibilityForModal(true);
}
if (targetWindow.isVisible()) {
ensureModalWindowInteractive(targetWindow);
return;
}
showModalWindow(targetWindow);
};
const waitForModalOpen = async (modal: OverlayHostedModal, timeoutMs: number): Promise<boolean> =>
@@ -515,6 +588,7 @@ export function createOverlayModalRuntimeService(
});
return {
primeModalWindow,
sendToActiveOverlayWindow,
openRuntimeOptionsPalette,
openJimaku,