diff --git a/frontend/src/lib/components/file/BulkTagEditor.svelte b/frontend/src/lib/components/file/BulkTagEditor.svelte index 38d4a89..444422c 100644 --- a/frontend/src/lib/components/file/BulkTagEditor.svelte +++ b/frontend/src/lib/components/file/BulkTagEditor.svelte @@ -116,6 +116,51 @@ busy = false; } } + + // ---- Keyboard navigation (from the search input) ---- + // ↓/↑ highlight a suggestion, Enter adds it (focus stays); with the input empty + // ←/→ walk the assigned tags and Del removes the focused one from all files. + let highlightIdx = $state(0); + let assignedFocusIdx = $state(-1); + + $effect(() => { + if (highlightIdx > availableTags.length - 1) { + highlightIdx = Math.max(0, availableTags.length - 1); + } + }); + + function onSearchKeydown(e: KeyboardEvent) { + if (e.key === 'ArrowDown') { + e.preventDefault(); + assignedFocusIdx = -1; + if (availableTags.length) highlightIdx = Math.min(highlightIdx + 1, availableTags.length - 1); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + assignedFocusIdx = -1; + highlightIdx = Math.max(highlightIdx - 1, 0); + } else if (e.key === 'Enter') { + const tag = availableTags[highlightIdx]; + if (tag?.id) { + e.preventDefault(); + void add(tag.id); + } + } else if (e.key === 'ArrowRight' && search === '') { + e.preventDefault(); + const n = assignedTags.length; + if (n) assignedFocusIdx = assignedFocusIdx < 0 ? 0 : Math.min(assignedFocusIdx + 1, n - 1); + } else if (e.key === 'ArrowLeft' && search === '') { + e.preventDefault(); + const n = assignedTags.length; + if (n) assignedFocusIdx = assignedFocusIdx < 0 ? n - 1 : Math.max(assignedFocusIdx - 1, 0); + } else if (e.key === 'Delete' && assignedFocusIdx >= 0) { + const tag = assignedTags[assignedFocusIdx]; + if (tag?.id) { + e.preventDefault(); + void remove(tag.id); + assignedFocusIdx = Math.min(assignedFocusIdx, assignedTags.length - 2); + } + } + }