Files
website/src/lib/components/Input/Search.svelte
bytedream e30446598c
All checks were successful
delpoy / build-and-deploy (push) Successful in 51s
use global modal for popup messages
2024-12-02 21:02:23 +01:00

109 lines
3.1 KiB
Svelte

<script lang="ts">
let {
id,
value = $bindable(),
inputValue = $bindable(),
suggestionRequired = false,
emptyAllowed = false,
searchSuggestionFunc = () => Promise.resolve([]),
invalidMessage,
size = 'md',
label,
required = false,
onsubmit
}: {
id?: string;
value?: string;
inputValue?: string;
suggestionRequired?: boolean;
emptyAllowed?: boolean;
searchSuggestionFunc?: (input: string) => Promise<{ name: string; value: string }[]>;
invalidMessage?: string;
size?: 'xs' | 'sm' | 'md' | 'lg';
label?: string;
required?: boolean;
onsubmit?: (event: Event & { input: string; value: string }) => void;
} = $props();
let searchSuggestions: { name: string; value: string }[] = $state([]);
$effect(() => {
if (!suggestionRequired) value = inputValue;
});
</script>
<div class="relative">
<div>
{#if label}
<label class="label" for={id}>
<span class="label-text">
{label}
{#if required}
<span class="text-red-700">*</span>
{/if}
</span>
</label>
{/if}
<input
type="search"
class="input input-bordered w-full"
class:input-xs={size === 'xs'}
class:input-sm={size === 'sm'}
class:input-md={size === 'md'}
class:input-lg={size === 'lg'}
{id}
{required}
bind:value={inputValue}
oninput={(e: Event & { currentTarget: EventTarget & HTMLInputElement }) => {
value = '';
searchSuggestionFunc(inputValue).then((v) => {
searchSuggestions = v;
const searchSuggestion = v.find((v) => v.name === inputValue);
if (searchSuggestion !== undefined) {
inputValue = searchSuggestion.name;
value = searchSuggestion.value;
searchSuggestions = [];
e.currentTarget.setCustomValidity('');
onsubmit?.(Object.assign(e, { input: inputValue, value: value }));
} else if (inputValue === '' && emptyAllowed) {
onsubmit?.(Object.assign(e, { input: '', value: '' }));
}
});
}}
oninvalid={(e: Event & { currentTarget: EventTarget & HTMLInputElement }) => {
if (invalidMessage) e.currentTarget.setCustomValidity(invalidMessage);
}}
pattern={suggestionRequired
? `${value ? inputValue : 'a^' + (emptyAllowed ? '|$^' : '')}`
: null}
/>
</div>
{#if inputValue && searchSuggestions.length !== 0}
<ul class="absolute bg-base-200 w-full z-20 menu menu-sm rounded-box">
{#each searchSuggestions as searchSuggestion}
<li class="w-full text-left">
<button
class="block w-full overflow-hidden text-ellipsis whitespace-nowrap"
title="{searchSuggestion.name} ({searchSuggestion.value})"
onclick={(e) => {
inputValue = searchSuggestion.name;
value = searchSuggestion.value;
searchSuggestions = [];
onsubmit?.(Object.assign(e, { input: inputValue, value: value }));
}}>{searchSuggestion.name}</button
>
</li>
{/each}
</ul>
{/if}
</div>
<!-- close the search suggestions box when clicking outside -->
{#if inputValue && searchSuggestions.length !== 0}
<button
aria-label=" "
class="absolute top-0 left-0 z-10 w-full h-full cursor-default"
onclick={() => (searchSuggestions = [])}
></button>
{/if}