feat(modal): return focus to tag input after accepting a suggestion
Accepting an auto-suggested tag (Suggestions panel or the autocomplete dropdown) left focus on <body>, so the operator had to re-click the tag field to add the next one. Expose TagAutocomplete.focus (the existing mobile-aware focusInput) and call it after accept from both paths; SuggestionsPanel emits 'accepted' for the parent to refocus. Operator-asked 2026-06-08. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,9 @@ import SuggestionsCategoryGroup from './SuggestionsCategoryGroup.vue'
|
|||||||
import AliasPickerDialog from './AliasPickerDialog.vue'
|
import AliasPickerDialog from './AliasPickerDialog.vue'
|
||||||
|
|
||||||
const props = defineProps({ imageId: { type: Number, required: true } })
|
const props = defineProps({ imageId: { type: Number, required: true } })
|
||||||
|
// 'accepted' lets the parent return focus to the tag input after a suggestion is
|
||||||
|
// applied (operator-asked 2026-06-08).
|
||||||
|
const emit = defineEmits(['accepted'])
|
||||||
const store = useSuggestionsStore()
|
const store = useSuggestionsStore()
|
||||||
const modal = useModalStore()
|
const modal = useModalStore()
|
||||||
|
|
||||||
@@ -72,6 +75,7 @@ async function onAccept(s) {
|
|||||||
try {
|
try {
|
||||||
await store.accept(s)
|
await store.accept(s)
|
||||||
await modal.reloadTags()
|
await modal.reloadTags()
|
||||||
|
emit('accepted')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast({ text: `Accept failed: ${e.message}`, type: 'error' })
|
toast({ text: `Accept failed: ${e.message}`, type: 'error' })
|
||||||
}
|
}
|
||||||
@@ -85,6 +89,7 @@ async function onAliasConfirm(canonicalTagId) {
|
|||||||
await store.aliasAccept(aliasTarget.value, canonicalTagId)
|
await store.aliasAccept(aliasTarget.value, canonicalTagId)
|
||||||
aliasDialog.value = false
|
aliasDialog.value = false
|
||||||
await modal.reloadTags()
|
await modal.reloadTags()
|
||||||
|
emit('accepted')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast({ text: `Alias failed: ${e.message}`, type: 'error' })
|
toast({ text: `Alias failed: ${e.message}`, type: 'error' })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,10 @@ function focusInput () {
|
|||||||
nextTick(() => inputRef.value?.focus?.())
|
nextTick(() => inputRef.value?.focus?.())
|
||||||
}
|
}
|
||||||
onMounted(focusInput)
|
onMounted(focusInput)
|
||||||
|
// Exposed so the parent (TagPanel) can hand focus back to this field after an
|
||||||
|
// auto-suggestion is accepted, keeping the keyboard flow on the input instead
|
||||||
|
// of dropping to <body> (operator-asked 2026-06-08). Mobile guard preserved.
|
||||||
|
defineExpose({ focus: focusInput })
|
||||||
|
|
||||||
// Single text input; no kind dropdown. Client-side mirror of the
|
// Single text input; no kind dropdown. Client-side mirror of the
|
||||||
// backend's parse_kind_prefix lives below — kept in sync with
|
// backend's parse_kind_prefix lives below — kept in sync with
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
<v-divider class="my-3" />
|
<v-divider class="my-3" />
|
||||||
|
|
||||||
<TagAutocomplete
|
<TagAutocomplete
|
||||||
|
ref="tagInputRef"
|
||||||
@pick-existing="onPickExisting" @pick-new="onPickNew"
|
@pick-existing="onPickExisting" @pick-new="onPickNew"
|
||||||
@accept-suggestion="onAcceptSuggestion"
|
@accept-suggestion="onAcceptSuggestion"
|
||||||
/>
|
/>
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
<SuggestionsPanel
|
<SuggestionsPanel
|
||||||
v-if="modal.currentImageId != null"
|
v-if="modal.currentImageId != null"
|
||||||
:image-id="modal.currentImageId"
|
:image-id="modal.currentImageId"
|
||||||
|
@accepted="focusTagInput"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<v-dialog v-model="renameDialog" max-width="420">
|
<v-dialog v-model="renameDialog" max-width="420">
|
||||||
@@ -60,6 +62,12 @@ import FandomSetDialog from './FandomSetDialog.vue'
|
|||||||
const modal = useModalStore()
|
const modal = useModalStore()
|
||||||
const suggestions = useSuggestionsStore()
|
const suggestions = useSuggestionsStore()
|
||||||
const errorMsg = ref(null)
|
const errorMsg = ref(null)
|
||||||
|
const tagInputRef = ref(null)
|
||||||
|
|
||||||
|
// Return focus to the tag input after a suggestion is accepted (from the
|
||||||
|
// Suggestions panel or the autocomplete dropdown) so the operator can keep
|
||||||
|
// typing the next tag without re-clicking the field (operator-asked 2026-06-08).
|
||||||
|
function focusTagInput() { tagInputRef.value?.focus?.() }
|
||||||
|
|
||||||
async function onRemove(tagId) {
|
async function onRemove(tagId) {
|
||||||
errorMsg.value = null
|
errorMsg.value = null
|
||||||
@@ -84,6 +92,7 @@ async function onAcceptSuggestion(s) {
|
|||||||
try {
|
try {
|
||||||
await suggestions.accept(s)
|
await suggestions.accept(s)
|
||||||
await modal.reloadTags()
|
await modal.reloadTags()
|
||||||
|
focusTagInput()
|
||||||
} catch (e) { errorMsg.value = e.message }
|
} catch (e) { errorMsg.value = e.message }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user