Spaces:
Sleeping
Sleeping
calycekr
commited on
feat(search): improve keyboard and mouse event handling for better UX (#1846)
Browse files* feat(search): improve keyboard and mouse event handling for better user experience
- Improved Event Handling Mechanism
- Added Event Handling for `handleBackdropClick` and ESC Key
- Added Support for Apple macOS Command Key
* fix: fix lint
src/lib/components/chat/Search.svelte
CHANGED
|
@@ -10,18 +10,17 @@
|
|
| 10 |
import { base } from "$app/paths";
|
| 11 |
|
| 12 |
import { debounce } from "$lib/utils/debounce";
|
| 13 |
-
import { onDestroy, onMount } from "svelte";
|
| 14 |
|
| 15 |
import type { GETSearchEndpointReturn } from "../../../routes/api/conversations/search/+server";
|
| 16 |
import NavConversationItem from "../NavConversationItem.svelte";
|
| 17 |
import { titles } from "../NavMenu.svelte";
|
| 18 |
import { beforeNavigate } from "$app/navigation";
|
| 19 |
-
import { browser } from "$app/environment";
|
| 20 |
|
| 21 |
import CarbonClose from "~icons/carbon/close";
|
| 22 |
import { fly } from "svelte/transition";
|
| 23 |
import InfiniteScroll from "../InfiniteScroll.svelte";
|
| 24 |
|
|
|
|
| 25 |
let inputElement: HTMLInputElement | undefined = $state(undefined);
|
| 26 |
|
| 27 |
let searchInput: string = $state("");
|
|
@@ -66,6 +65,15 @@
|
|
| 66 |
}
|
| 67 |
}, 300);
|
| 68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
async function handleVisible(v: string) {
|
| 70 |
const newConvs = await fetch(`${base}/api/conversations/search?q=${v}&p=${page++}`)
|
| 71 |
.then(async (r) => {
|
|
@@ -91,29 +99,28 @@
|
|
| 91 |
|
| 92 |
$effect(() => update(searchInput));
|
| 93 |
|
| 94 |
-
|
| 95 |
-
if (
|
| 96 |
-
searchOpen
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
| 99 |
}
|
| 100 |
-
}
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
beforeNavigate(() => {
|
| 108 |
searchOpen = false;
|
| 109 |
searchInput = "";
|
| 110 |
});
|
| 111 |
|
| 112 |
-
onDestroy(() => {
|
| 113 |
-
if (!browser) return;
|
| 114 |
-
window.removeEventListener("keydown", openSearchListener);
|
| 115 |
-
});
|
| 116 |
-
|
| 117 |
$effect(() => {
|
| 118 |
if (searchOpen) {
|
| 119 |
inputElement?.focus();
|
|
@@ -128,8 +135,11 @@
|
|
| 128 |
});
|
| 129 |
</script>
|
| 130 |
|
|
|
|
|
|
|
| 131 |
{#if searchOpen}
|
| 132 |
<div
|
|
|
|
| 133 |
class="fixed bottom-0 left-[5%] right-[5%] top-[10%] z-50
|
| 134 |
m-4 mx-auto h-fit max-w-2xl
|
| 135 |
overflow-hidden rounded-xl
|
|
@@ -151,6 +161,7 @@
|
|
| 151 |
type="text"
|
| 152 |
name="searchbar"
|
| 153 |
placeholder="Search for chats..."
|
|
|
|
| 154 |
class={{
|
| 155 |
"h-12 w-full p-4 text-lg dark:bg-gray-800 dark:text-gray-200": true,
|
| 156 |
"border-b border-b-gray-500/50": searchInput && searchInput.length >= 3,
|
|
|
|
| 10 |
import { base } from "$app/paths";
|
| 11 |
|
| 12 |
import { debounce } from "$lib/utils/debounce";
|
|
|
|
| 13 |
|
| 14 |
import type { GETSearchEndpointReturn } from "../../../routes/api/conversations/search/+server";
|
| 15 |
import NavConversationItem from "../NavConversationItem.svelte";
|
| 16 |
import { titles } from "../NavMenu.svelte";
|
| 17 |
import { beforeNavigate } from "$app/navigation";
|
|
|
|
| 18 |
|
| 19 |
import CarbonClose from "~icons/carbon/close";
|
| 20 |
import { fly } from "svelte/transition";
|
| 21 |
import InfiniteScroll from "../InfiniteScroll.svelte";
|
| 22 |
|
| 23 |
+
let searchContainer: HTMLDivElement | undefined = $state(undefined);
|
| 24 |
let inputElement: HTMLInputElement | undefined = $state(undefined);
|
| 25 |
|
| 26 |
let searchInput: string = $state("");
|
|
|
|
| 65 |
}
|
| 66 |
}, 300);
|
| 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 |
+
|
| 77 |
async function handleVisible(v: string) {
|
| 78 |
const newConvs = await fetch(`${base}/api/conversations/search?q=${v}&p=${page++}`)
|
| 79 |
.then(async (r) => {
|
|
|
|
| 99 |
|
| 100 |
$effect(() => update(searchInput));
|
| 101 |
|
| 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();
|
| 109 |
}
|
|
|
|
| 110 |
|
| 111 |
+
if (searchOpen && event.key === "Escape") {
|
| 112 |
+
if (searchOpen) {
|
| 113 |
+
searchOpen = false;
|
| 114 |
+
}
|
| 115 |
+
event.preventDefault();
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
|
| 119 |
beforeNavigate(() => {
|
| 120 |
searchOpen = false;
|
| 121 |
searchInput = "";
|
| 122 |
});
|
| 123 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
$effect(() => {
|
| 125 |
if (searchOpen) {
|
| 126 |
inputElement?.focus();
|
|
|
|
| 135 |
});
|
| 136 |
</script>
|
| 137 |
|
| 138 |
+
<svelte:window onkeydown={handleKeydown} onmousedown={handleBackdropClick} />
|
| 139 |
+
|
| 140 |
{#if searchOpen}
|
| 141 |
<div
|
| 142 |
+
bind:this={searchContainer}
|
| 143 |
class="fixed bottom-0 left-[5%] right-[5%] top-[10%] z-50
|
| 144 |
m-4 mx-auto h-fit max-w-2xl
|
| 145 |
overflow-hidden rounded-xl
|
|
|
|
| 161 |
type="text"
|
| 162 |
name="searchbar"
|
| 163 |
placeholder="Search for chats..."
|
| 164 |
+
autocomplete="off"
|
| 165 |
class={{
|
| 166 |
"h-12 w-full p-4 text-lg dark:bg-gray-800 dark:text-gray-200": true,
|
| 167 |
"border-b border-b-gray-500/50": searchInput && searchInput.length >= 3,
|