Skip to content

Commit ed62a55

Browse files
authored
docs: Use proper svelte 5 syntax in search modal (#244)
* ref(docs): Use svelte 5 runes syntax * lint * fix lint errors
1 parent cb3ab58 commit ed62a55

File tree

1 file changed

+55
-41
lines changed

1 file changed

+55
-41
lines changed

docs/src/components/SearchModal.svelte

Lines changed: 55 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
<script lang="ts">
2-
import { onMount } from 'svelte';
32
import DOMPurify from 'dompurify';
3+
44
interface WindowWithPagefind {
55
pagefind?: {
66
search: (query: string) => Promise<PagefindSearchResponse>;
77
init: () => Promise<void>;
88
};
99
attributeIndex?: AttributeIndex[];
1010
}
11+
1112
interface 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
5760
async 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
126134
let 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 = [];
@@ -169,7 +179,7 @@ $: {
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();
@@ -190,7 +200,7 @@ $: {
190200
191201
isLoading = false;
192202
}, 100);
193-
}
203+
});
194204
195205
function 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
250263
function 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

Comments
 (0)