|
10 | 10 | import { base } from "$app/paths"; |
11 | 11 |
|
12 | 12 | import { debounce } from "$lib/utils/debounce"; |
13 | | - import { onDestroy, onMount } from "svelte"; |
14 | 13 |
|
15 | 14 | import type { GETSearchEndpointReturn } from "../../../routes/api/conversations/search/+server"; |
16 | 15 | import NavConversationItem from "../NavConversationItem.svelte"; |
17 | 16 | import { titles } from "../NavMenu.svelte"; |
18 | 17 | import { beforeNavigate } from "$app/navigation"; |
19 | | - import { browser } from "$app/environment"; |
20 | 18 |
|
21 | 19 | import CarbonClose from "~icons/carbon/close"; |
22 | 20 | import { fly } from "svelte/transition"; |
23 | 21 | import InfiniteScroll from "../InfiniteScroll.svelte"; |
24 | 22 |
|
| 23 | + let searchContainer: HTMLDivElement | undefined = $state(undefined); |
25 | 24 | let inputElement: HTMLInputElement | undefined = $state(undefined); |
26 | 25 |
|
27 | 26 | let searchInput: string = $state(""); |
|
66 | 65 | } |
67 | 66 | }, 300); |
68 | 67 |
|
| 68 | + const handleBackdropClick = (event: MouseEvent) => { |
| 69 | + if (!searchOpen || !searchContainer) return; |
| 70 | +
|
| 71 | + const target = event.target; |
| 72 | + if (!(target instanceof Node) || !searchContainer.contains(target)) { |
| 73 | + searchOpen = false; |
| 74 | + } |
| 75 | + }; |
| 76 | +
|
69 | 77 | async function handleVisible(v: string) { |
70 | 78 | const newConvs = await fetch(`${base}/api/conversations/search?q=${v}&p=${page++}`) |
71 | 79 | .then(async (r) => { |
|
91 | 99 |
|
92 | 100 | $effect(() => update(searchInput)); |
93 | 101 |
|
94 | | - async function openSearchListener(ev: KeyboardEvent) { |
95 | | - if (ev.ctrlKey && ev.key === "k") { |
96 | | - searchOpen = true; |
97 | | - ev.stopPropagation(); |
98 | | - ev.preventDefault(); |
| 102 | + function handleKeydown(event: KeyboardEvent) { |
| 103 | + if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") { |
| 104 | + if (!searchOpen) { |
| 105 | + searchOpen = true; |
| 106 | + } |
| 107 | + event.preventDefault(); |
| 108 | + event.stopPropagation(); |
99 | 109 | } |
100 | | - } |
101 | 110 |
|
102 | | - onMount(() => { |
103 | | - if (!browser) return; |
104 | | - window.addEventListener("keydown", openSearchListener); |
105 | | - }); |
| 111 | + if (searchOpen && event.key === "Escape") { |
| 112 | + if (searchOpen) { |
| 113 | + searchOpen = false; |
| 114 | + } |
| 115 | + event.preventDefault(); |
| 116 | + } |
| 117 | + } |
106 | 118 |
|
107 | 119 | beforeNavigate(() => { |
108 | 120 | searchOpen = false; |
109 | 121 | searchInput = ""; |
110 | 122 | }); |
111 | 123 |
|
112 | | - onDestroy(() => { |
113 | | - if (!browser) return; |
114 | | - window.removeEventListener("keydown", openSearchListener); |
115 | | - }); |
116 | | -
|
117 | 124 | $effect(() => { |
118 | 125 | if (searchOpen) { |
119 | 126 | inputElement?.focus(); |
|
128 | 135 | }); |
129 | 136 | </script> |
130 | 137 |
|
| 138 | +<svelte:window onkeydown={handleKeydown} onmousedown={handleBackdropClick} /> |
| 139 | + |
131 | 140 | {#if searchOpen} |
132 | 141 | <div |
| 142 | + bind:this={searchContainer} |
133 | 143 | class="fixed bottom-0 left-[5%] right-[5%] top-[10%] z-50 |
134 | 144 | m-4 mx-auto h-fit max-w-2xl |
135 | 145 | overflow-hidden rounded-xl |
|
151 | 161 | type="text" |
152 | 162 | name="searchbar" |
153 | 163 | placeholder="Search for chats..." |
| 164 | + autocomplete="off" |
154 | 165 | class={{ |
155 | 166 | "h-12 w-full p-4 text-lg dark:bg-gray-800 dark:text-gray-200": true, |
156 | 167 | "border-b border-b-gray-500/50": searchInput && searchInput.length >= 3, |
|
0 commit comments