11<script setup lang="ts">
2+ import { onClickOutside } from ' @vueuse/core'
23import { LinkBase } from ' #components'
34import type { NavigationConfig , NavigationConfigWithGroups } from ' ~/types'
45import { isEditableElement } from ' ~/utils/input'
@@ -143,6 +144,7 @@ const route = useRoute()
143144const isMobile = useIsMobile ()
144145const isSearchExpandedManually = shallowRef (false )
145146const searchBoxRef = useTemplateRef (' searchBoxRef' )
147+ const headerRef = useTemplateRef (' headerRef' )
146148
147149// On search page, always show search expanded on mobile
148150const isOnHomePage = computed (() => route .name === ' index' )
@@ -156,6 +158,21 @@ function expandMobileSearch() {
156158 })
157159}
158160
161+ function collapseMobileSearch() {
162+ if (! isMobile .value ) return
163+ if (isOnSearchPage .value ) return
164+ isSearchExpandedManually .value = false
165+ if (document .activeElement instanceof HTMLElement ) {
166+ document .activeElement .blur ()
167+ }
168+ }
169+
170+ onClickOutside (headerRef , () => {
171+ if (isMobile .value && isSearchExpanded .value && ! isOnSearchPage .value ) {
172+ collapseMobileSearch ()
173+ }
174+ })
175+
159176watch (
160177 isOnSearchPage ,
161178 visible => {
@@ -171,13 +188,6 @@ watch(
171188
172189function handleSearchBlur() {
173190 showFullSearch .value = false
174- // Collapse expanded search on mobile after blur (with delay for click handling)
175- // But don't collapse if we're on the search page
176- if (isMobile .value && ! isOnSearchPage .value ) {
177- setTimeout (() => {
178- isSearchExpandedManually .value = false
179- }, 150 )
180- }
181191}
182192
183193function handleSearchFocus() {
@@ -200,10 +210,22 @@ onKeyStroke(
200210 },
201211 { dedupe: true },
202212)
213+
214+ onKeyStroke (
215+ e =>
216+ isKeyWithoutModifiers (e , ' Escape' ) &&
217+ isMobile .value &&
218+ isSearchExpanded .value &&
219+ ! isOnSearchPage .value ,
220+ e => {
221+ e .preventDefault ()
222+ collapseMobileSearch ()
223+ },
224+ )
203225 </script >
204226
205227<template >
206- <header class =" sticky top-0 z-50 border-b border-border" >
228+ <header ref = " headerRef " class =" sticky top-0 z-50 border-b border-border" >
207229 <div class =" absolute inset-0 bg-bg/80 backdrop-blur-md" />
208230 <nav
209231 :aria-label =" $t('nav.main_navigation')"
@@ -301,15 +323,25 @@ onKeyStroke(
301323
302324 <!-- Mobile: Search button (expands search) -->
303325 <ButtonBase
326+ v-if =" !isSearchExpanded && !isOnHomePage"
304327 type =" button"
305328 class =" sm:hidden ms-auto"
306329 :aria-label =" $t('nav.tap_to_search')"
307- :aria-expanded =" showMobileMenu "
330+ :aria-expanded =" isSearchExpanded "
308331 @click =" expandMobileSearch"
309- v-if =" !isSearchExpanded && !isOnHomePage"
310332 classicon =" i-lucide:search"
311333 />
312334
335+ <!-- Mobile: Close search button (collapses search) -->
336+ <ButtonBase
337+ v-if =" isSearchExpanded && !isOnSearchPage"
338+ type =" button"
339+ class =" sm:hidden flex-shrink-0"
340+ :aria-label =" $t('nav.close_search')"
341+ @click =" collapseMobileSearch"
342+ classicon =" i-lucide:x"
343+ />
344+
313345 <!-- Mobile: Menu button (always visible, click to open menu) -->
314346 <ButtonBase
315347 type =" button"
0 commit comments