|
9 | 9 | import { apiModulesStore } from '$lib/stores/apiContext'; |
10 | 10 | import { ApiToc } from '$lib/components/api'; |
11 | 11 | import ExamplesToc from '$lib/components/examples/ExamplesToc.svelte'; |
12 | | - import { search, hybridSearch, type SearchResult } from '$lib/utils/search'; |
| 12 | + import type { SearchResult } from '$lib/utils/search'; |
| 13 | + import { createDebouncedSearch } from '$lib/stores/searchHook'; |
13 | 14 | import { searchTarget } from '$lib/stores/searchNavigation'; |
14 | 15 | import { exampleGroupsStore } from '$lib/stores/examplesContext'; |
15 | 16 | import type { PackageManifest } from '$lib/api/versions'; |
|
35 | 36 | (item) => item.title !== 'Examples' || hasExamples |
36 | 37 | ) |
37 | 38 | ); |
38 | | - let searchQuery = $state(''); |
39 | | - let debouncedQuery = $state(''); |
40 | | - let searchResults = $state<SearchResult[]>([]); |
41 | | - let isSearching = $state(false); |
42 | | - let showResults = $derived(searchQuery.length > 0); |
43 | 39 |
|
44 | | - // Debounce search query (150ms delay) |
45 | | - $effect(() => { |
46 | | - const query = searchQuery; |
47 | | - const timeout = setTimeout(() => { |
48 | | - debouncedQuery = query; |
49 | | - }, 150); |
50 | | - return () => clearTimeout(timeout); |
51 | | - }); |
52 | | -
|
53 | | - // Run hybrid search when debounced query changes |
54 | | - $effect(() => { |
55 | | - const query = debouncedQuery; |
56 | | - if (!query) { |
57 | | - searchResults = []; |
58 | | - isSearching = false; |
59 | | - return; |
60 | | - } |
61 | | -
|
62 | | - // Track if this search is still relevant |
63 | | - let cancelled = false; |
64 | | -
|
65 | | - // Run hybrid search with semantic fallback |
66 | | - hybridSearch(query, 15, () => { |
67 | | - // Callback when semantic search starts |
68 | | - if (!cancelled) { |
69 | | - isSearching = true; |
70 | | - } |
71 | | - }).then((result) => { |
72 | | - if (!cancelled) { |
73 | | - searchResults = result.results; |
74 | | - isSearching = false; |
75 | | - } |
76 | | - }).catch(() => { |
77 | | - if (!cancelled) { |
78 | | - // Fallback to keyword search on error |
79 | | - searchResults = search(query, 15); |
80 | | - isSearching = false; |
81 | | - } |
82 | | - }); |
83 | | -
|
84 | | - return () => { |
85 | | - cancelled = true; |
86 | | - }; |
87 | | - }); |
| 40 | + // Use shared search hook |
| 41 | + const searchState = createDebouncedSearch({ limit: 15 }); |
88 | 42 | let searchInput = $state<HTMLInputElement | null>(null); |
89 | 43 |
|
90 | 44 | function handleGlobalKeydown(event: KeyboardEvent) { |
|
115 | 69 |
|
116 | 70 | function handleSearchKeydown(event: KeyboardEvent) { |
117 | 71 | if (event.key === 'Escape') { |
118 | | - if (searchQuery) { |
119 | | - searchQuery = ''; |
| 72 | + if (searchState.query) { |
| 73 | + searchState.clear(); |
120 | 74 | } else { |
121 | 75 | searchInput?.blur(); |
122 | 76 | } |
|
125 | 79 | } |
126 | 80 |
|
127 | 81 | function handleResultClick(result: SearchResult) { |
128 | | - searchQuery = ''; |
| 82 | + searchState.clear(); |
129 | 83 | // Set the target element to expand/scroll to |
130 | 84 | searchTarget.set({ |
131 | 85 | name: result.name, |
|
141 | 95 | <div class="sidebar-fixed"> |
142 | 96 | <div class="search-container"> |
143 | 97 | <SearchInput |
144 | | - bind:value={searchQuery} |
| 98 | + bind:value={searchState.query} |
145 | 99 | bind:inputRef={searchInput} |
146 | | - {isSearching} |
| 100 | + isSearching={searchState.isSearching} |
147 | 101 | onkeydown={handleSearchKeydown} |
148 | 102 | /> |
149 | 103 | </div> |
150 | 104 |
|
151 | | - {#if showResults} |
| 105 | + {#if searchState.showResults} |
152 | 106 | <nav class="sidebar-nav search-results"> |
153 | | - {#if searchResults.length > 0} |
154 | | - {#each searchResults as result} |
| 107 | + {#if searchState.results.length > 0} |
| 108 | + {#each searchState.results as result} |
155 | 109 | <SearchResultComponent {result} variant="list" onclick={() => handleResultClick(result)} /> |
156 | 110 | {/each} |
157 | 111 | {:else} |
|
178 | 132 |
|
179 | 133 | </div> |
180 | 134 |
|
181 | | - {#if !showResults && isApiPage && $apiModulesStore.length > 0} |
| 135 | + {#if !searchState.showResults && isApiPage && $apiModulesStore.length > 0} |
182 | 136 | <div class="sidebar-scrollable with-separator"> |
183 | 137 | <ApiToc modules={$apiModulesStore} /> |
184 | 138 | </div> |
185 | | - {:else if !showResults && isExamplesPage && $exampleGroupsStore.length > 0} |
| 139 | + {:else if !searchState.showResults && isExamplesPage && $exampleGroupsStore.length > 0} |
186 | 140 | <div class="sidebar-scrollable with-separator"> |
187 | 141 | <ExamplesToc groups={$exampleGroupsStore} {packageId} currentTag={currentTag} /> |
188 | 142 | </div> |
|
0 commit comments