diff --git a/frontend/src/lib/components/file/BulkTagEditor.svelte b/frontend/src/lib/components/file/BulkTagEditor.svelte index 444422c..5695d35 100644 --- a/frontend/src/lib/components/file/BulkTagEditor.svelte +++ b/frontend/src/lib/components/file/BulkTagEditor.svelte @@ -159,6 +159,20 @@ void remove(tag.id); assignedFocusIdx = Math.min(assignedFocusIdx, assignedTags.length - 2); } + } else if (e.key === 'Escape') { + // Staged exit: a non-empty filter clears first; once empty, Escape + // releases focus. Stop propagation so neither step reaches the page's + // window handler — only the *next* Escape (with focus already gone) does, + // and that one closes the popup. + e.preventDefault(); + e.stopPropagation(); + if (search) { + search = ''; + assignedFocusIdx = -1; + } else { + assignedFocusIdx = -1; + (e.currentTarget as HTMLInputElement).blur(); + } } } diff --git a/frontend/src/lib/components/file/FileViewer.svelte b/frontend/src/lib/components/file/FileViewer.svelte index 1ef95c0..a936447 100644 --- a/frontend/src/lib/components/file/FileViewer.svelte +++ b/frontend/src/lib/components/file/FileViewer.svelte @@ -184,9 +184,17 @@ } // ---- Keyboard ---- + let viewerPage = $state(); let tagsSection = $state(); let pendingTagFocus = false; + // Bring the preview back to the top of the scroll container (the overlay, or + // the page in the standalone route). scrollIntoView resolves the right + // scroller in either case. Called when Escape leaves the tag filter. + function revealPreview() { + viewerPage?.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + function handleKeydown(e: KeyboardEvent) { if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return; if (e.ctrlKey || e.metaKey || e.altKey) return; @@ -242,7 +250,7 @@ -
+