Hopefully more robust header autoclose logic to avoid OSD keyboard causing a just-focused search bar getting hidden.
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user