From c9b7f0701bdab3293a0931fda896e7f7d62375ed Mon Sep 17 00:00:00 2001 From: Masahiko AMANO Date: Wed, 17 Jun 2026 17:15:12 +0300 Subject: [PATCH] feat(frontend): keyboard control for the add-to-pool dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The file viewer could only open the pool picker via its top-right button — there was no `p` shortcut there (only the grid had one), so pressing `p` on the view page did nothing. Add `p` to open the picker from the viewer, and give the picker itself full keyboard control: `/` focuses the search box, arrows move a highlight through the pool list, Enter adds to the highlighted pool, and Escape clears the search first, then closes. Both the viewer and the grid now yield the keyboard entirely to the open picker (the picker owns Escape via its own window handler) so the clear-then-close behaviour isn't pre-empted by the host's own Escape. Co-Authored-By: Claude Opus 4.8 --- .../src/lib/components/file/FileViewer.svelte | 21 +++--- .../src/lib/components/file/PoolPicker.svelte | 75 ++++++++++++++++++- frontend/src/routes/files/+page.svelte | 2 +- 3 files changed, 83 insertions(+), 15 deletions(-) diff --git a/frontend/src/lib/components/file/FileViewer.svelte b/frontend/src/lib/components/file/FileViewer.svelte index 417d86a..3ac5cd5 100644 --- a/frontend/src/lib/components/file/FileViewer.svelte +++ b/frontend/src/lib/components/file/FileViewer.svelte @@ -237,17 +237,11 @@ } function handleKeydown(e: KeyboardEvent) { - // While the pool picker is open it owns the keyboard: Escape closes it - // (even from its search field), and every other key is swallowed so the - // viewer's shortcuts don't fire behind the modal. Typing still works — - // non-Escape keys aren't prevented, only ignored here. - if (poolPickerOpen) { - if (e.key === 'Escape') { - e.preventDefault(); - poolPickerOpen = false; - } - return; - } + // While the pool picker is open it owns the keyboard entirely (its own + // window handler drives search focus, arrow navigation, Enter to add, and + // Escape to clear-then-close). Yield so the viewer's shortcuts don't fire + // behind the modal and don't race the picker for Escape. + if (poolPickerOpen) return; if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return; if (e.ctrlKey || e.metaKey || e.altKey) return; // Letter keys are matched by physical position (e.code) so j/k/e work on any @@ -259,6 +253,11 @@ } else if (e.code === 'KeyE') { e.preventDefault(); jumpToTags(); + } else if (e.code === 'KeyP') { + if (file) { + e.preventDefault(); + poolPickerOpen = true; + } } else if (e.key === 'Escape') { onClose(); } diff --git a/frontend/src/lib/components/file/PoolPicker.svelte b/frontend/src/lib/components/file/PoolPicker.svelte index 0068653..973bb9c 100644 --- a/frontend/src/lib/components/file/PoolPicker.svelte +++ b/frontend/src/lib/components/file/PoolPicker.svelte @@ -1,5 +1,6 @@ + + @@ -98,10 +158,15 @@ {#if filtered.length === 0}

No pools found.

{:else} -
    - {#each filtered as pool (pool.id)} +
      + {#each filtered as pool, i (pool.id)}
    • - @@ -226,6 +291,10 @@ background-color: color-mix(in srgb, var(--color-accent) 12%, transparent); } + .picker-item.highlighted { + background-color: color-mix(in srgb, var(--color-accent) 22%, transparent); + } + .picker-item-name { flex: 1; font-size: 0.95rem; diff --git a/frontend/src/routes/files/+page.svelte b/frontend/src/routes/files/+page.svelte index 89c2b3e..c715655 100644 --- a/frontend/src/routes/files/+page.svelte +++ b/frontend/src/routes/files/+page.svelte @@ -148,7 +148,7 @@ function handleKey(e: KeyboardEvent) { if (e.key === 'Escape') { if (tagEditorOpen) tagEditorOpen = false; - else if (poolPickerOpen) poolPickerOpen = false; + else if (poolPickerOpen) return; // PoolPicker owns Escape (clear search, then close) else if (confirmDeleteFiles) confirmDeleteFiles = false; else if (activeFileId) return; else if ($selectionActive) selectionStore.exit();