feat(frontend): stage Escape in the tag editors to clear, then exit

In the single-file viewer's tag filter, Escape now clears a non-empty
filter first; on an empty filter it blurs and scrolls the preview back
to the top. In the bulk (multi-file) editor it clears, then releases
focus, so only the next Escape reaches the page handler and closes the
popup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 21:27:06 +03:00
parent ca3bca59e7
commit 6a3bb9ff51
3 changed files with 37 additions and 5 deletions
@@ -184,9 +184,17 @@
}
// ---- Keyboard ----
let viewerPage = $state<HTMLElement>();
let tagsSection = $state<HTMLElement>();
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 @@
<svelte:window onkeydown={handleKeydown} />
<div class="viewer-page">
<div class="viewer-page" bind:this={viewerPage}>
<!-- Top bar -->
<div class="top-bar">
<button class="back-btn" onclick={onClose} aria-label="Back to files">
@@ -377,7 +385,7 @@
<section class="section" use:tagsSentinel bind:this={tagsSection}>
<div class="field-label">Tags</div>
{#if tagsLoaded}
<TagPicker {fileTags} onAdd={addTag} onRemove={removeTag} />
<TagPicker {fileTags} onAdd={addTag} onRemove={removeTag} onExit={revealPreview} />
{:else}
<p class="tags-loading">Loading tags…</p>
{/if}