mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
feat(web): combobox accessibility improvements (#8007)
* bump skip link z index, to prevent overlap with the search box * combobox refactor initial commit * pull label into the combobox component * feat(web): combobox accessibility improvements * fix: replace crypto.randomUUID, fix border UI bug, simpler focus handling (#2) * fix: handle changes in the selected option * fix: better escape key handling in search bar * fix: remove broken tailwind classes Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com> * fix: remove custom "outclick" handler logic * fix: use focusout instead of custom key handlers to detect focus change * fix: move escape key handling to the window Also add escape key handling to the input box, to make sure that the "recent searches" dropdown gets closed too. * fix: better input event handling Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com> * fix: highlighting selected dropdown element --------- Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
This commit is contained in:
parent
033f83a55a
commit
c6d2408517
8 changed files with 214 additions and 79 deletions
|
|
@ -11,6 +11,7 @@
|
|||
import type { MetadataSearchDto, SmartSearchDto } from '@immich/sdk';
|
||||
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
import { shortcut } from '$lib/utils/shortcut';
|
||||
|
||||
export let value = '';
|
||||
export let grayTheme: boolean;
|
||||
|
|
@ -84,7 +85,16 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<div class="w-full relative" use:clickOutside on:outclick={onFocusOut} on:escape={onFocusOut}>
|
||||
<svelte:window
|
||||
use:shortcut={{
|
||||
shortcut: { key: 'Escape' },
|
||||
onShortcut: () => {
|
||||
onFocusOut();
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="w-full relative" use:clickOutside={{ onOutclick: onFocusOut }}>
|
||||
<form
|
||||
draggable="false"
|
||||
autocomplete="off"
|
||||
|
|
@ -118,6 +128,12 @@
|
|||
bind:this={input}
|
||||
on:click={onFocusIn}
|
||||
disabled={showFilter}
|
||||
use:shortcut={{
|
||||
shortcut: { key: 'Escape' },
|
||||
onShortcut: () => {
|
||||
onFocusOut();
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="absolute inset-y-0 {showClearIcon ? 'right-14' : 'right-5'} flex items-center pl-6 transition-all">
|
||||
|
|
|
|||
|
|
@ -40,24 +40,24 @@
|
|||
|
||||
<div class="grid grid-cols-[repeat(auto-fit,minmax(10rem,1fr))] gap-5 mt-1">
|
||||
<div class="w-full">
|
||||
<label class="text-sm text-black dark:text-white" for="search-camera-make">Make</label>
|
||||
<Combobox
|
||||
id="search-camera-make"
|
||||
options={toComboBoxOptions(makes)}
|
||||
selectedOption={makeFilter ? { label: makeFilter, value: makeFilter } : undefined}
|
||||
id="camera-make"
|
||||
label="Make"
|
||||
on:select={({ detail }) => (filters.make = detail?.value)}
|
||||
options={toComboBoxOptions(makes)}
|
||||
placeholder="Search camera make..."
|
||||
selectedOption={makeFilter ? { label: makeFilter, value: makeFilter } : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<label class="text-sm text-black dark:text-white" for="search-camera-model">Model</label>
|
||||
<Combobox
|
||||
id="search-camera-model"
|
||||
options={toComboBoxOptions(models)}
|
||||
selectedOption={modelFilter ? { label: modelFilter, value: modelFilter } : undefined}
|
||||
id="camera-model"
|
||||
label="Model"
|
||||
on:select={({ detail }) => (filters.model = detail?.value)}
|
||||
options={toComboBoxOptions(models)}
|
||||
placeholder="Search camera model..."
|
||||
selectedOption={modelFilter ? { label: modelFilter, value: modelFilter } : undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -62,35 +62,35 @@
|
|||
|
||||
<div class="grid grid-cols-[repeat(auto-fit,minmax(10rem,1fr))] gap-5 mt-1">
|
||||
<div class="w-full">
|
||||
<label class="text-sm text-black dark:text-white" for="search-place-country">Country</label>
|
||||
<Combobox
|
||||
id="search-place-country"
|
||||
options={toComboBoxOptions(countries)}
|
||||
selectedOption={filters.country ? { label: filters.country, value: filters.country } : undefined}
|
||||
id="location-country"
|
||||
label="Country"
|
||||
on:select={({ detail }) => (filters.country = detail?.value)}
|
||||
options={toComboBoxOptions(countries)}
|
||||
placeholder="Search country..."
|
||||
selectedOption={filters.country ? { label: filters.country, value: filters.country } : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<label class="text-sm text-black dark:text-white" for="search-place-state">State</label>
|
||||
<Combobox
|
||||
id="search-place-state"
|
||||
options={toComboBoxOptions(states)}
|
||||
selectedOption={filters.state ? { label: filters.state, value: filters.state } : undefined}
|
||||
id="location-state"
|
||||
label="State"
|
||||
on:select={({ detail }) => (filters.state = detail?.value)}
|
||||
options={toComboBoxOptions(states)}
|
||||
placeholder="Search state..."
|
||||
selectedOption={filters.state ? { label: filters.state, value: filters.state } : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<label class="text-sm text-black dark:text-white" for="search-place-city">City</label>
|
||||
<Combobox
|
||||
id="search-place-city"
|
||||
options={toComboBoxOptions(cities)}
|
||||
selectedOption={filters.city ? { label: filters.city, value: filters.city } : undefined}
|
||||
id="location-city"
|
||||
label="City"
|
||||
on:select={({ detail }) => (filters.city = detail?.value)}
|
||||
options={toComboBoxOptions(cities)}
|
||||
placeholder="Search city..."
|
||||
selectedOption={filters.city ? { label: filters.city, value: filters.city } : undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue