diff --git a/web/src/lib/components/shared-components/combobox.svelte b/web/src/lib/components/shared-components/combobox.svelte index 19fd73d25f..28a2b7253d 100644 --- a/web/src/lib/components/shared-components/combobox.svelte +++ b/web/src/lib/components/shared-components/combobox.svelte @@ -18,6 +18,7 @@ import type { FormEventHandler } from 'svelte/elements'; import { shortcuts } from '$lib/utils/shortcut'; import { clickOutside } from '$lib/utils/click-outside'; + import { focusOutside } from '$lib/utils/focus-outside'; /** * Unique identifier for the combobox. @@ -40,6 +41,7 @@ let searchQuery = selectedOption?.label || ''; let selectedIndex: number | undefined; let optionRefs: HTMLElement[] = []; + let input: HTMLInputElement; const inputId = `combobox-${id}`; const listboxId = `listbox-${id}`; @@ -51,7 +53,6 @@ const dispatch = createEventDispatcher<{ select: ComboBoxOption | undefined; - click: void; }>(); const activate = () => { @@ -101,6 +102,8 @@ }; const onClear = () => { + input?.focus(); + selectedIndex = undefined; selectedOption = undefined; searchQuery = ''; dispatch('select', selectedOption); @@ -111,11 +114,16 @@
{ - if (e.relatedTarget instanceof Node && !e.currentTarget.contains(e.relatedTarget)) { - deactivate(); - } - }} + use:focusOutside={{ onFocusOut: deactivate }} + use:shortcuts={[ + { + shortcut: { key: 'Escape' }, + onShortcut: (event) => { + event.stopPropagation(); + closeDropdown(); + }, + }, + ]} >
{#if isActive} @@ -133,6 +141,7 @@ aria-controls={listboxId} aria-expanded={isOpen} autocomplete="off" + bind:this={input} class:!pl-8={isActive} class:!rounded-b-none={isOpen} class:cursor-pointer={!isActive} @@ -213,9 +222,7 @@ role="option" aria-selected={selectedIndex === 0} aria-disabled={true} - class:bg-gray-100={selectedIndex === 0} - class:dark:bg-gray-700={selectedIndex === 0} - class="text-left w-full px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-default" + class="text-left w-full px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-default aria-selected:bg-gray-100 aria-selected:dark:bg-gray-700" id={`${listboxId}-${0}`} on:click={() => closeDropdown()} > diff --git a/web/src/lib/components/shared-components/search-bar/search-bar.svelte b/web/src/lib/components/shared-components/search-bar/search-bar.svelte index 8afa56df71..6c057483d4 100644 --- a/web/src/lib/components/shared-components/search-bar/search-bar.svelte +++ b/web/src/lib/components/shared-components/search-bar/search-bar.svelte @@ -12,6 +12,7 @@ import { getMetadataSearchQuery } from '$lib/utils/metadata-search'; import { handlePromiseError } from '$lib/utils'; import { shortcut } from '$lib/utils/shortcut'; + import { focusOutside } from '$lib/utils/focus-outside'; export let value = ''; export let grayTheme: boolean; @@ -94,7 +95,7 @@ }} /> -
+
void; +} + +export function focusOutside(node: HTMLElement, options: Options = {}) { + const { onFocusOut } = options; + + const handleFocusOut = (event: FocusEvent) => { + if (onFocusOut && event.relatedTarget instanceof Node && !node.contains(event.relatedTarget as Node)) { + onFocusOut(); + } + }; + + node.addEventListener('focusout', handleFocusOut); + + return { + destroy() { + node.removeEventListener('focusout', handleFocusOut); + }, + }; +}