fix: strip playback accelerators whenever any text input is focused (#2059)

The command-palette-specific IPC approach didn't cover other modals
with inputs (settings search, playlist creation, etc.). Replace it
with document-level focusin/focusout listeners that signal the main
process whenever any input/textarea/contenteditable gains or loses
focus, so the menu rebuild is triggered automatically for all current
and future input surfaces.

Co-authored-by: muckymucky <muckymucky@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
muckymucky
2026-05-25 21:35:45 +01:00
committed by GitHub
parent 61c6036d41
commit 22d37135ae
4 changed files with 49 additions and 14 deletions
+6 -6
View File
@@ -280,12 +280,12 @@ let currentRepeatMode: PlayerRepeat = PlayerRepeat.NONE;
let currentSidebarCollapsed = false; let currentSidebarCollapsed = false;
let currentShuffleEnabled = false; let currentShuffleEnabled = false;
let playbackMenuAccelerators: MenuPlaybackState['accelerators'] = {}; let playbackMenuAccelerators: MenuPlaybackState['accelerators'] = {};
let commandPaletteOpen = false; let inputFocused = false;
ipcMain.on('command-palette-state', (_event, opened: boolean) => { ipcMain.on('input-focus-state', (_event, focused: boolean) => {
const next = !!opened; const next = !!focused;
if (commandPaletteOpen === next) return; if (inputFocused === next) return;
commandPaletteOpen = next; inputFocused = next;
if (isMacOS()) { if (isMacOS()) {
rebuildMainMenu(); rebuildMainMenu();
} }
@@ -350,7 +350,7 @@ const rebuildMainMenu = () => {
if (!menuBuilder || !mainWindow) return; if (!menuBuilder || !mainWindow) return;
menuBuilder.buildMenu({ menuBuilder.buildMenu({
accelerators: commandPaletteOpen ? {} : playbackMenuAccelerators, accelerators: inputFocused ? {} : playbackMenuAccelerators,
playbackStatus: currentPlaybackStatus, playbackStatus: currentPlaybackStatus,
privateMode: currentPrivateMode, privateMode: currentPrivateMode,
repeatMode: currentRepeatMode, repeatMode: currentRepeatMode,
+3 -3
View File
@@ -54,8 +54,8 @@ const forceGarbageCollection = (): boolean => {
} }
}; };
const setCommandPaletteOpen = (opened: boolean) => { const setInputFocused = (focused: boolean) => {
ipcRenderer.send('command-palette-state', opened); ipcRenderer.send('input-focus-state', focused);
}; };
const rendererOpenSettings = (cb: () => void) => { const rendererOpenSettings = (cb: () => void) => {
@@ -105,7 +105,7 @@ export const utils = {
rendererTogglePrivateMode, rendererTogglePrivateMode,
rendererToggleSidebar, rendererToggleSidebar,
rendererUpdateAvailable, rendererUpdateAvailable,
setCommandPaletteOpen, setInputFocused,
startPowerSaveBlocker, startPowerSaveBlocker,
stopPowerSaveBlocker, stopPowerSaveBlocker,
}; };
+40
View File
@@ -93,6 +93,7 @@ const AppEffects = () => (
<GlobalShortcutsEffect /> <GlobalShortcutsEffect />
<LanguageEffect /> <LanguageEffect />
<NativeMenuSyncEffect /> <NativeMenuSyncEffect />
<InputFocusEffect />
</> </>
); );
@@ -170,3 +171,42 @@ const NativeMenuSyncEffect = () => {
return null; return null;
}; };
const InputFocusEffect = () => {
useEffect(() => {
if (!isElectron()) return;
const handleFocusIn = (e: FocusEvent) => {
const target = e.target as Element | null;
if (
target instanceof HTMLInputElement ||
target instanceof HTMLTextAreaElement ||
(target instanceof HTMLElement && target.isContentEditable)
) {
window.api?.utils?.setInputFocused?.(true);
}
};
const handleFocusOut = (e: FocusEvent) => {
const related = e.relatedTarget as Element | null;
if (
related instanceof HTMLInputElement ||
related instanceof HTMLTextAreaElement ||
(related instanceof HTMLElement && related.isContentEditable)
) {
return;
}
window.api?.utils?.setInputFocused?.(false);
};
document.addEventListener('focusin', handleFocusIn);
document.addEventListener('focusout', handleFocusOut);
return () => {
document.removeEventListener('focusin', handleFocusIn);
document.removeEventListener('focusout', handleFocusOut);
};
}, []);
return null;
};
-5
View File
@@ -201,22 +201,17 @@ export const useAppStore = createWithEqualityFn<AppSlice>()(
set((state) => { set((state) => {
state.commandPalette.opened = false; state.commandPalette.opened = false;
}); });
window.api?.utils?.setCommandPaletteOpen?.(false);
}, },
open: () => { open: () => {
set((state) => { set((state) => {
state.commandPalette.opened = true; state.commandPalette.opened = true;
}); });
window.api?.utils?.setCommandPaletteOpen?.(true);
}, },
opened: false, opened: false,
toggle: () => { toggle: () => {
let nextOpened = false;
set((state) => { set((state) => {
state.commandPalette.opened = !state.commandPalette.opened; state.commandPalette.opened = !state.commandPalette.opened;
nextOpened = state.commandPalette.opened;
}); });
window.api?.utils?.setCommandPaletteOpen?.(nextOpened);
}, },
}, },
commandPaletteSearchSectionsExpanded: {}, commandPaletteSearchSectionsExpanded: {},