Hopefully more robust header autoclose logic to avoid OSD keyboard causing a just-focused search bar getting hidden.

This commit is contained in:
Leo Vasanko
2025-09-25 08:00:12 -06:00
parent 8a508f273d
commit 6c396bab61

View File

@@ -1,7 +1,13 @@
<template> <template>
<div class="header-controls-wrapper"> <div class="header-controls-wrapper">
<Transition name="header-controls" appear> <Transition name="header-controls" appear>
<div v-if="isVisible" class="header-controls"> <div
v-if="isVisible"
ref="headerControlsRef"
class="header-controls"
@focusin="handleFocusIn"
@focusout="handleFocusOut"
>
<div class="search-with-spacer"> <div class="search-with-spacer">
<!-- Shrinkable spacer to align search with week label column; smoothly shrinks as needed --> <!-- Shrinkable spacer to align search with week label column; smoothly shrinks as needed -->
<div class="pre-search-spacer" aria-hidden="true"></div> <div class="pre-search-spacer" aria-hidden="true"></div>
@@ -69,7 +75,7 @@
</template> </template>
<script setup> <script setup>
import { computed, ref, onMounted, onBeforeUnmount, defineExpose, nextTick } from 'vue' import { computed, ref, onMounted, onBeforeUnmount, defineExpose, nextTick, watch } from 'vue'
import { useCalendarStore } from '@/stores/CalendarStore' import { useCalendarStore } from '@/stores/CalendarStore'
import { formatTodayString } from '@/utils/date' import { formatTodayString } from '@/utils/date'
import EventSearch from '@/components/Search.vue' import EventSearch from '@/components/Search.vue'
@@ -122,18 +128,19 @@ function goToToday() {
// Screen size detection and visibility toggle // Screen size detection and visibility toggle
const isVisible = ref(false) const isVisible = ref(false)
// Track if we auto-opened due to a find (Ctrl/Cmd+F) const headerControlsRef = ref(null)
const autoOpenedForSearch = ref(false) const hasFocusWithin = ref(false)
function checkScreenSize() { function checkScreenSize() {
const isSmallScreen = window.innerHeight < 600 const isSmallScreen = window.innerHeight < 600
// Default to open on large screens, closed on small screens isVisible.value = !isSmallScreen || hasFocusWithin.value
isVisible.value = !isSmallScreen
} }
function toggleVisibility() { function toggleVisibility() {
isVisible.value = !isVisible.value isVisible.value = !isVisible.value
if (!isVisible.value) autoOpenedForSearch.value = false if (!isVisible.value) {
hasFocusWithin.value = false
}
} }
// Settings dialog integration // Settings dialog integration
@@ -150,6 +157,25 @@ function openSettings() {
// Search component ref exposure // Search component ref exposure
const eventSearchRef = ref(null) const eventSearchRef = ref(null)
function handleFocusIn() {
hasFocusWithin.value = true
if (!isVisible.value) isVisible.value = true
}
function handleFocusOut(event) {
const container = headerControlsRef.value
if (!container) {
hasFocusWithin.value = false
return
}
const nextTarget = event.relatedTarget ?? document.activeElement
if (nextTarget && container.contains(nextTarget)) return
hasFocusWithin.value = false
if (window.innerHeight < 600) {
checkScreenSize()
}
}
function focusSearch(selectAll = true) { function focusSearch(selectAll = true) {
eventSearchRef.value?.focusSearch(selectAll) eventSearchRef.value?.focusSearch(selectAll)
} }
@@ -167,24 +193,21 @@ function handleGlobalFind(e) {
e.preventDefault() e.preventDefault()
if (!isVisible.value) { if (!isVisible.value) {
isVisible.value = true isVisible.value = true
autoOpenedForSearch.value = true
} else {
autoOpenedForSearch.value = false
} }
// Defer focus until after transition renders input // Defer focus until after transition renders input
nextTick(() => requestAnimationFrame(() => focusSearch(true))) nextTick(() => focusSearch(true))
} }
} }
function handleSearchActivate(r) { function handleSearchActivate(r) {
emit('search-activate', r) emit('search-activate', r)
// Auto close only if we auto-opened for search shortcut
if (autoOpenedForSearch.value) {
isVisible.value = false
}
autoOpenedForSearch.value = false
} }
watch(isVisible, (visible) => {
if (visible) nextTick(() => focusSearch(true))
else hasFocusWithin.value = false
})
onMounted(() => { onMounted(() => {
checkScreenSize() checkScreenSize()
window.addEventListener('resize', checkScreenSize) window.addEventListener('resize', checkScreenSize)