calycekr commited on
Commit
ac12cc3
·
unverified ·
1 Parent(s): e926497

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

Files changed (1) hide show
  1. src/lib/components/chat/Search.svelte +28 -17
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
- async function openSearchListener(ev: KeyboardEvent) {
95
- if (ev.ctrlKey && ev.key === "k") {
96
- searchOpen = true;
97
- ev.stopPropagation();
98
- ev.preventDefault();
 
 
99
  }
100
- }
101
 
102
- onMount(() => {
103
- if (!browser) return;
104
- window.addEventListener("keydown", openSearchListener);
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,