refactor: (4) enhance MarketSearchBar with internal query handling and debouncing

- Introduced an internal query state to improve UI responsiveness during search input.
- Implemented a debounced search function to optimize performance and reduce unnecessary emissions.
- Updated conditions for displaying keyboard hints and clear button based on the new internal query.
- Ensured both internal and actual search queries are cleared appropriately.

These changes enhance the user experience by providing immediate feedback while typing and optimizing search operations.
This commit is contained in:
padreug 2025-09-27 18:44:02 +02:00
parent 1f321dce4a
commit b3428c2905

View file

@ -8,7 +8,7 @@
<Input <Input
ref="searchInputRef" ref="searchInputRef"
:model-value="searchQuery" :model-value="internalQuery || searchQuery"
@update:model-value="handleSearchChange" @update:model-value="handleSearchChange"
@keydown="handleKeydown" @keydown="handleKeydown"
@focus="handleFocus" @focus="handleFocus"
@ -21,13 +21,13 @@
<!-- Enhanced Features (keyboard hints, clear button) --> <!-- Enhanced Features (keyboard hints, clear button) -->
<div class="absolute inset-y-0 right-0 pr-3 flex items-center gap-2"> <div class="absolute inset-y-0 right-0 pr-3 flex items-center gap-2">
<!-- Keyboard Shortcuts Hint (only for enhanced mode on desktop) --> <!-- Keyboard Shortcuts Hint (only for enhanced mode on desktop) -->
<div v-if="showEnhancements && showKeyboardHints && !searchQuery && isDesktop" class="text-xs text-muted-foreground flex items-center gap-1"> <div v-if="showEnhancements && showKeyboardHints && !internalQuery && !searchQuery && isDesktop" class="text-xs text-muted-foreground flex items-center gap-1">
<Badge variant="outline" class="px-1 py-0 text-xs"> K</Badge> <Badge variant="outline" class="px-1 py-0 text-xs"> K</Badge>
</div> </div>
<!-- Clear Button --> <!-- Clear Button -->
<Button <Button
v-if="searchQuery" v-if="internalQuery || searchQuery"
variant="ghost" variant="ghost"
size="sm" size="sm"
@click="handleClear" @click="handleClear"
@ -89,7 +89,7 @@ import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { Search, X } from 'lucide-vue-next' import { Search, X } from 'lucide-vue-next'
import { useLocalStorage, useBreakpoints, breakpointsTailwind } from '@vueuse/core' import { useLocalStorage, useBreakpoints, breakpointsTailwind, useDebounceFn } from '@vueuse/core'
import type { Product } from '../types/market' import type { Product } from '../types/market'
// Conditional imports for enhanced mode // Conditional imports for enhanced mode
@ -265,20 +265,35 @@ const searchSuggestions = computed(() => {
}) })
// Methods // Methods
const handleSearchChange = (value: string | number) => { // Internal search query for immediate UI updates
const stringValue = String(value) const internalQuery = ref('')
setSearchQuery(stringValue)
emit('update:modelValue', stringValue) // Debounced search function for performance optimization
emit('search', stringValue) const debouncedSearch = useDebounceFn((value: string) => {
setSearchQuery(value)
emit('search', value)
emit('results', filteredItems.value) emit('results', filteredItems.value)
// Add to recent searches when user finishes typing (enhanced mode only) // Add to recent searches when user finishes typing (enhanced mode only)
if (props.showEnhancements && stringValue.trim() && stringValue.length >= 3) { if (props.showEnhancements && value.trim() && value.length >= 3) {
addToRecentSearches(stringValue.trim()) addToRecentSearches(value.trim())
} }
}, 300)
const handleSearchChange = (value: string | number) => {
const stringValue = String(value)
// Update internal query immediately for responsive UI
internalQuery.value = stringValue
emit('update:modelValue', stringValue)
// Debounce the actual search computation
debouncedSearch(stringValue)
} }
const handleClear = () => { const handleClear = () => {
// Clear both internal and actual search queries
internalQuery.value = ''
clearSearch() clearSearch()
emit('update:modelValue', '') emit('update:modelValue', '')
emit('search', '') emit('search', '')
@ -297,7 +312,7 @@ const handleKeydown = (event: KeyboardEvent) => {
// Handle basic Escape key for simple mode // Handle basic Escape key for simple mode
if (event.key === 'Escape') { if (event.key === 'Escape') {
event.preventDefault() event.preventDefault()
if (searchQuery.value) { if (internalQuery.value || searchQuery.value) {
handleClear() handleClear()
} }
return return
@ -307,7 +322,7 @@ const handleKeydown = (event: KeyboardEvent) => {
if (useSearchKeyboardShortcuts.value && handleSearchKeydown) { if (useSearchKeyboardShortcuts.value && handleSearchKeydown) {
const shouldClear = handleSearchKeydown(event) const shouldClear = handleSearchKeydown(event)
if (shouldClear) { if (shouldClear) {
if (searchQuery.value) { if (internalQuery.value || searchQuery.value) {
handleClear() handleClear()
} else { } else {
blurSearchInput() blurSearchInput()
@ -318,15 +333,18 @@ const handleKeydown = (event: KeyboardEvent) => {
const filterByCategory = (category: string) => { const filterByCategory = (category: string) => {
emit('filter-category', category) emit('filter-category', category)
internalQuery.value = category
setSearchQuery(category) setSearchQuery(category)
} }
const applySuggestion = (suggestion: string) => { const applySuggestion = (suggestion: string) => {
internalQuery.value = suggestion
setSearchQuery(suggestion) setSearchQuery(suggestion)
addToRecentSearches(suggestion) addToRecentSearches(suggestion)
} }
const applyRecentSearch = (recent: string) => { const applyRecentSearch = (recent: string) => {
internalQuery.value = recent
setSearchQuery(recent) setSearchQuery(recent)
} }