Skip to content

Commit

Permalink
Debounce searching for tabs in the stash list
Browse files Browse the repository at this point in the history
This makes the search experience more performant when there are many,
many tabs to be searched, because fewer searches have to be performed,
and the text box feels more responsive.
  • Loading branch information
josh-berry committed Sep 3, 2023
1 parent 10c9490 commit 8501768
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 12 deletions.
41 changes: 29 additions & 12 deletions src/components/search-input.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
:aria-label="ariaLabel ?? 'Search'"
:title="props.tooltip"
:placeholder="props.placeholder"
v-model="modelValue"
v-model="searchContent"
@keydown.esc.prevent="clear"
/>
<button
v-if="modelValue !== ''"
v-if="searchContent !== ''"
class="clear"
aria-label="Clear Search"
title="Clear search"
Expand All @@ -21,7 +21,7 @@
</template>
<script lang="ts">
import {computed, ref} from "vue";
import {ref, watch} from "vue";
</script>
<script setup lang="ts">
Expand All @@ -31,19 +31,36 @@ const props = defineProps<{
ariaLabel?: string;
modelValue: string;
debounceMs?: number;
}>();
const emit = defineEmits<{
(e: "update:modelValue", v: string): void;
}>();
const modelValue = computed({
get(): string {
return props.modelValue;
},
set(v: string) {
emit("update:modelValue", v);
},
// searchContent tracks what's actually in the text box at any given moment. We
// watch searchContent and emit modelValue updates so that we can debounce
// updates--this way, component users can do more expensive things (like
// re-searching) in their modelValue event handlers without it impacting
// performance as much.
const searchContent = ref("");
let debounceTimeout: number | undefined = undefined;
watch(searchContent, () => {
// If debounce is disabled or we're clearing the search field, emit the update
// immediately. We never want to debounce on clear, because it usually means
// the user is done searching.
if (!props.debounceMs || searchContent.value === "") {
emit("update:modelValue", searchContent.value);
return;
}

if (debounceTimeout !== undefined) clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(() => {
emit("update:modelValue", searchContent.value);
clearTimeout(debounceTimeout);
debounceTimeout = undefined;
}, props.debounceMs);
});

const $search = ref(undefined! as HTMLInputElement);
Expand All @@ -55,12 +72,12 @@ defineExpose({
});

function clear(ev: KeyboardEvent | MouseEvent) {
if (modelValue.value !== "") {
if (searchContent.value !== "") {
// We consume the "clear" event only if we want to clear the search box,
// because if the search box is present in a dialog/menu/etc., hitting "Esc"
// twice will both clear the search AND close the parent modal.
ev.stopPropagation();
emit("update:modelValue", "");
searchContent.value = "";
}
}
</script>
1 change: 1 addition & 0 deletions src/stash-list/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
:tooltip="searchTooltip"
:placeholder="search_placeholder"
v-model="searchText"
:debounce-ms="250"
/>
<a
:class="{action: true, collapse: !collapsed, expand: collapsed}"
Expand Down

0 comments on commit 8501768

Please sign in to comment.