11<script lang =" ts" >
2- import { onMount } from ' svelte' ;
32import DOMPurify from ' dompurify' ;
3+
44interface WindowWithPagefind {
55 pagefind? : {
66 search: (query : string ) => Promise <PagefindSearchResponse >;
77 init: () => Promise <void >;
88 };
99 attributeIndex? : AttributeIndex [];
1010}
11+
1112interface PagefindResult {
1213 id: string ;
1314 data: () => Promise <{
@@ -40,19 +41,21 @@ interface AttributeIndex {
4041 deprecated: boolean ;
4142}
4243
43- let isOpen = false ;
44- let query = ' ' ;
45- let attributeResults: AttributeIndex [] = [];
46- let pageResults: SearchResult [] = [];
47- let selectedIndex = 0 ;
48- let isLoading = false ;
49- let inputEl: HTMLInputElement ;
50- let resultsEl: HTMLDivElement ;
51- let usingKeyboard = false ;
52-
53- $ : totalResults = attributeResults .length + pageResults .length ;
54- $ : hasResults = attributeResults .length > 0 || pageResults .length > 0 ;
55- $ : noResults = query && ! isLoading && ! hasResults ;
44+ let isOpen = $state (false );
45+ let query = $state (' ' );
46+ let attributeResults = $state <AttributeIndex []>([]);
47+ let pageResults = $state <SearchResult []>([]);
48+ let selectedIndex = $state (0 );
49+ let isLoading = $state (false );
50+ // biome-ignore lint/style/useConst: <false flag by biome. We bind this state to an element and it needs to be mutable>
51+ let inputEl: HTMLInputElement | undefined = $state ();
52+ // biome-ignore lint/style/useConst: <false flag by biome. We bind this state to an element and it needs to be mutable>
53+ let resultsEl: HTMLDivElement | undefined = $state ();
54+ let usingKeyboard = $state (false );
55+
56+ const totalResults = $derived (attributeResults .length + pageResults .length );
57+ const hasResults = $derived (attributeResults .length > 0 || pageResults .length > 0 );
58+ const noResults = $derived (query && ! isLoading && ! hasResults );
5659
5760async function loadAttributeIndex() {
5861 const windowWithPagefind = window as WindowWithPagefind ;
@@ -102,7 +105,8 @@ function handleTriggerClick() {
102105 isOpen = true ;
103106}
104107
105- onMount (() => {
108+ // Setup global event listeners
109+ $effect (() => {
106110 document .addEventListener (' keydown' , handleGlobalKeyDown );
107111 const trigger = document .getElementById (' search-trigger' );
108112 trigger ?.addEventListener (' click' , handleTriggerClick );
@@ -113,22 +117,28 @@ onMount(() => {
113117 };
114118});
115119
116- $ : if (isOpen ) {
117- loadAttributeIndex ();
118- loadPagefind ();
119- query = ' ' ;
120- attributeResults = [];
121- pageResults = [];
122- selectedIndex = 0 ;
123- setTimeout (() => inputEl ?.focus (), 0 );
124- }
120+ // Handle modal open
121+ $effect (() => {
122+ if (isOpen ) {
123+ loadAttributeIndex ();
124+ loadPagefind ();
125+ query = ' ' ;
126+ attributeResults = [];
127+ pageResults = [];
128+ selectedIndex = 0 ;
129+ setTimeout (() => inputEl ?.focus (), 0 );
130+ }
131+ });
125132
133+ // Search effect
126134let searchTimeout: ReturnType <typeof setTimeout >;
127135
128- $ : {
136+ $effect (() => {
137+ const currentQuery = query ;
138+
129139 clearTimeout (searchTimeout );
130140 searchTimeout = setTimeout (async () => {
131- const trimmedQuery = query .trim ().toLowerCase ();
141+ const trimmedQuery = currentQuery .trim ().toLowerCase ();
132142
133143 if (! trimmedQuery ) {
134144 attributeResults = [];
169179 // Search pages with Pagefind (async)
170180 if (windowWithPagefind .pagefind ) {
171181 try {
172- const response = await windowWithPagefind .pagefind .search (query );
182+ const response = await windowWithPagefind .pagefind .search (currentQuery );
173183 const searchResults = await Promise .all (
174184 response .results .slice (0 , 5 ).map (async (result ) => {
175185 const data = await result .data ();
190200
191201 isLoading = false ;
192202 }, 100 );
193- }
203+ });
194204
195205function handleKeyDown(e : KeyboardEvent ) {
196206 if (e .key === ' ArrowDown' ) {
@@ -242,10 +252,13 @@ function navigateToResult(result: SearchResult) {
242252 window .location .href = result .url ;
243253}
244254
245- $ : if (resultsEl && selectedIndex >= 0 ) {
246- const selectedElement = resultsEl .querySelector (' .selected' ) as HTMLElement ;
247- selectedElement ?.scrollIntoView ({ block: ' nearest' });
248- }
255+ // Scroll selected item into view
256+ $effect (() => {
257+ if (resultsEl && selectedIndex >= 0 ) {
258+ const selectedElement = resultsEl .querySelector (' .selected' ) as HTMLElement ;
259+ selectedElement ?.scrollIntoView ({ block: ' nearest' });
260+ }
261+ });
249262
250263function highlightMatch(key : string , searchQuery : string ): { before: string ; match: string ; after: string } | null {
251264 const lowerKey = key .toLowerCase ();
@@ -264,14 +277,15 @@ function highlightMatch(key: string, searchQuery: string): { before: string; mat
264277
265278{#if isOpen }
266279 <!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
280+ <!-- svelte-ignore a11y_no_static_element_interactions -->
267281 <div
268282 class =" fixed inset-0 bg-black/70 backdrop-blur-sm z-[1000] flex items-start justify-center pt-[10vh]"
269- on:click ={() => isOpen = false }
283+ onclick ={() => isOpen = false }
270284 >
271285 <!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
272286 <div
273287 class =" w-full max-w-2xl bg-bg-secondary border border-border rounded-lg shadow-lg overflow-hidden mx-4"
274- on:click | stopPropagation
288+ onclick ={( e ) => e . stopPropagation ()}
275289 >
276290 <div class =" flex items-center gap-3 p-4 border-b border-border" >
277291 <svg class =" text-text-muted flex-shrink-0" width =" 20" height =" 20" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2" >
@@ -284,7 +298,7 @@ function highlightMatch(key: string, searchQuery: string): { before: string; mat
284298 class =" flex-1 bg-transparent border-none outline-none text-lg text-text-primary font-sans placeholder:text-text-muted"
285299 placeholder =" Search attributes (e.g., sentry.op, http.)"
286300 bind:value ={query }
287- on:keydown ={handleKeyDown }
301+ onkeydown ={handleKeyDown }
288302 />
289303 <kbd class =" px-2 py-1 bg-bg-elevated border border-border rounded-sm text-xs text-text-muted font-sans" >ESC</kbd >
290304 </div >
@@ -310,9 +324,9 @@ function highlightMatch(key: string, searchQuery: string): { before: string; mat
310324 {@const highlighted = highlightMatch (attr .key , query )}
311325 <button
312326 class ="flex flex-col gap-1 w-full px-4 py-3 bg-transparent border-none border-b border-border last:border-b-0 text-left cursor-pointer transition-all duration-fast border-l-2 {index === selectedIndex ? ' bg-accent/15 border-l-accent selected shadow-[inset_0_0_0_1px_rgba(149,128,255,0.2)]' : ' border-l-transparent hover:bg-bg-hover hover:border-l-border-light' }"
313- on:click ={() => navigateToAttribute (attr )}
314- on:mouseenter ={() => handleMouseEnter (index )}
315- on:mousemove ={handleMouseMove }
327+ onclick ={() => navigateToAttribute (attr )}
328+ onmouseenter ={() => handleMouseEnter (index )}
329+ onmousemove ={handleMouseMove }
316330 >
317331 <div class =" flex items-center justify-between gap-3 flex-wrap" >
318332 <code class =" font-mono text-sm font-medium bg-transparent p-0 border-none text-accent" >
@@ -346,9 +360,9 @@ function highlightMatch(key: string, searchQuery: string): { before: string; mat
346360 {@const actualIndex = attributeResults .length + index }
347361 <button
348362 class ="flex flex-col gap-1 w-full px-4 py-3 bg-transparent border-none border-b border-border last:border-b-0 text-left cursor-pointer transition-all duration-fast border-l-2 {actualIndex === selectedIndex ? ' bg-accent/15 border-l-accent selected shadow-[inset_0_0_0_1px_rgba(149,128,255,0.2)]' : ' border-l-transparent hover:bg-bg-hover hover:border-l-border-light' }"
349- on:click ={() => navigateToResult (result )}
350- on:mouseenter ={() => handleMouseEnter (actualIndex )}
351- on:mousemove ={handleMouseMove }
363+ onclick ={() => navigateToResult (result )}
364+ onmouseenter ={() => handleMouseEnter (actualIndex )}
365+ onmousemove ={handleMouseMove }
352366 >
353367 <span class ="text-sm font-medium {actualIndex === selectedIndex ? ' text-accent' : ' text-text-primary' }" >
354368 {result .title }
0 commit comments