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>
<div class="header-controls-wrapper">
<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">
<!-- Shrinkable spacer to align search with week label column; smoothly shrinks as needed -->
<div class="pre-search-spacer" aria-hidden="true"></div>
@@ -69,7 +75,7 @@
</template>
<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 { formatTodayString } from '@/utils/date'
import EventSearch from '@/components/Search.vue'
@@ -122,18 +128,19 @@ function goToToday() {
// Screen size detection and visibility toggle
const isVisible = ref(false)
// Track if we auto-opened due to a find (Ctrl/Cmd+F)
const autoOpenedForSearch = ref(false)
const headerControlsRef = ref(null)
const hasFocusWithin = ref(false)
function checkScreenSize() {
const isSmallScreen = window.innerHeight < 600
// Default to open on large screens, closed on small screens
isVisible.value = !isSmallScreen
isVisible.value = !isSmallScreen || hasFocusWithin.value
}
function toggleVisibility() {
isVisible.value = !isVisible.value
if (!isVisible.value) autoOpenedForSearch.value = false
if (!isVisible.value) {
hasFocusWithin.value = false
}
}
// Settings dialog integration
@@ -150,6 +157,25 @@ function openSettings() {
// Search component ref exposure
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) {
eventSearchRef.value?.focusSearch(selectAll)
}
@@ -167,24 +193,21 @@ function handleGlobalFind(e) {
e.preventDefault()
if (!isVisible.value) {
isVisible.value = true
autoOpenedForSearch.value = true
} else {
autoOpenedForSearch.value = false
}
// Defer focus until after transition renders input
nextTick(() => requestAnimationFrame(() => focusSearch(true)))
nextTick(() => focusSearch(true))
}
}
function handleSearchActivate(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(() => {
checkScreenSize()
window.addEventListener('resize', checkScreenSize)