From b79708524e470f5c284e9c09f5a4904bb82f57d3 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 7 Jun 2026 11:49:28 -0400 Subject: [PATCH] fix(modal): keyboard focus flow for the Pick-a-fandom dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Operator-specified flow for character-tag creation: focus starts in the fandom search dropdown; Tab moves to the new-fandom field where Enter creates; creating fills the dropdown and returns focus there; Enter in the dropdown accepts the selection. - Drive focus from the dialog's @after-enter (autofocus is unreliable inside a v-dialog — the focus-trap steals it post-mount); FandomPicker exposes focusSearch. - Drop the @update:model-value auto-confirm that closed the dialog the instant selectedId was set — that's what broke create-then-accept (creating set the value and immediately confirmed). Enter now accepts (menu-closed + value), while an open menu lets Vuetify pick the highlighted item first. - Tab from search → new-fandom field; Enter there creates, then focus returns to the dropdown for a single Enter-to-accept. - Restore focus to the tag input after the dialog confirms/cancels so the keyboard flow continues into the next tag. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/components/modal/FandomPicker.vue | 80 +++++++++++++++---- .../src/components/modal/TagAutocomplete.vue | 30 ++++++- 2 files changed, 90 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/modal/FandomPicker.vue b/frontend/src/components/modal/FandomPicker.vue index e1ac0bf..8f1147e 100644 --- a/frontend/src/components/modal/FandomPicker.vue +++ b/frontend/src/components/modal/FandomPicker.vue @@ -2,22 +2,37 @@ Pick a fandom - +

Or create a new fandom:

- + + Create
@@ -34,7 +49,7 @@ diff --git a/frontend/src/components/modal/TagAutocomplete.vue b/frontend/src/components/modal/TagAutocomplete.vue index efc8768..a63625f 100644 --- a/frontend/src/components/modal/TagAutocomplete.vue +++ b/frontend/src/components/modal/TagAutocomplete.vue @@ -48,8 +48,17 @@ - - + + + @@ -74,10 +83,11 @@ const store = useTagStore() // the modal's stacked-layout breakpoint (ImageViewer.vue). matchMedia is // safe on plain HTTP (not a secure-context API). const inputRef = ref(null) -onMounted(() => { +function focusInput () { if (window.matchMedia?.('(max-width: 900px)')?.matches) return nextTick(() => inputRef.value?.focus?.()) -}) +} +onMounted(focusInput) // Single text input; no kind dropdown. Client-side mirror of the // backend's parse_kind_prefix lives below — kept in sync with @@ -89,6 +99,7 @@ const hits = ref([]) const highlight = ref(0) const listRef = ref(null) const fandomDialog = ref(false) +const fandomPickerRef = ref(null) let pendingNewName = null const KNOWN_KINDS = new Set([ @@ -178,6 +189,17 @@ function onFandomChosen (fandom) { }) pendingNewName = null reset() + // Return focus to the tag input so the keyboard flow continues straight into + // the next tag instead of dropping to (operator-flagged 2026-06-07). + focusInput() +} + +// Cancelling the fandom dialog drops the pending character and hands focus back +// to the tag input, same as a successful pick. +function onFandomCancel () { + fandomDialog.value = false + pendingNewName = null + focusInput() } function onEnter () {