refactor(ui): unify confirm-dropdown Enter behavior via useAcceptOnEnter
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 3m8s
CI / backend-lint-and-test (push) Successful in 27s
CI / frontend-build (push) Successful in 1m44s

Operator-flagged (again) on the tag-merge picker: Enter on the dropdown re-opens
it instead of accepting the selection. I'd already patched this twice (fandom
picker + fandom set dialog) with copy-pasted capture-phase handlers, so DRY it.

New composable useAcceptOnEnter(accept): tracks the menu state and, on a
capture-phase Enter, lets Vuetify pick when the menu is open but calls accept()
(and blocks the re-open) when it's closed. Applied to every confirm-style picker:
- TagsView merge-into picker (the reported one)
- AliasPickerDialog
- PostSeriesMenu add-to-existing
- FandomPicker + FandomSetDialog (refactored off their bespoke handlers)

One behavior, one place to change it.
This commit is contained in:
2026-06-08 08:59:55 -04:00
parent f2fbe2ae6e
commit 408fcd488a
6 changed files with 71 additions and 33 deletions
@@ -0,0 +1,35 @@
import { ref } from 'vue'
/**
* Unifies keyboard behavior for confirm-style `v-autocomplete` / `v-select`
* dropdowns — a single picker whose Enter should trigger the surrounding
* modal's primary action (Merge, Add, Save, Use…).
*
* The recurring bug (operator-flagged across the merge / fandom / series / alias
* dialogs): pressing Enter with the menu CLOSED re-opens the dropdown instead of
* accepting the chosen value, because Vuetify's own Enter handler opens the menu.
* We bind in the CAPTURE phase so this runs BEFORE Vuetify's handler:
* - menu OPEN → return; let Vuetify pick the highlighted item (it sets the
* value and closes the menu).
* - menu CLOSED → preventDefault + stopPropagation so Vuetify can't re-open the
* menu, then call `accept()` — the modal's confirm. `accept`
* should itself no-op when nothing valid is selected.
*
* Usage:
* const { menuOpen, onEnter } = useAcceptOnEnter(onConfirm)
* <v-autocomplete v-model:menu="menuOpen" @keydown.enter.capture="onEnter" … />
*
* `menuOpen` is exposed so a component that programmatically focuses the field
* can force the menu shut (so the very next Enter accepts rather than being
* swallowed as a menu interaction).
*/
export function useAcceptOnEnter(accept) {
const menuOpen = ref(false)
function onEnter(e) {
if (menuOpen.value) return
e.preventDefault()
e.stopPropagation()
accept()
}
return { menuOpen, onEnter }
}