<script lang="ts"> let { id, value = $bindable(), suggestionRequired = false, emptyAllowed = false, searchSuggestionFunc = () => Promise.resolve([]), invalidMessage, size = 'md', label, required = false, onsubmit }: { id?: string; value: 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 elemValue = $state(value); let searchSuggestions: { name: string; value: string }[] = $state([]); </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" autocomplete="off" 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={elemValue} oninput={async (e: Event & { currentTarget: EventTarget & HTMLInputElement }) => { searchSuggestions = await searchSuggestionFunc(elemValue); const searchSuggestion = searchSuggestions.find((v) => v.name === elemValue); if (searchSuggestion !== undefined) { elemValue = searchSuggestion.name; value = searchSuggestion.value; searchSuggestions = []; (e.currentTarget || e.target).setCustomValidity(''); onsubmit?.(Object.assign(e, { input: elemValue, value: value })); } else if (elemValue === '' && emptyAllowed) { onsubmit?.(Object.assign(e, { input: '', value: '' })); } else { value = ''; } }} oninvalid={(e: Event & { currentTarget: EventTarget & HTMLInputElement }) => { if (invalidMessage) e.currentTarget.setCustomValidity(invalidMessage); }} onfocus={() => searchSuggestionFunc(elemValue).then((v) => (searchSuggestions = v))} pattern={suggestionRequired ? `${value ? elemValue : 'a^' + (emptyAllowed ? '|$^' : '')}` : null} /> </div> {#if elemValue && 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) => { elemValue = searchSuggestion.name; value = searchSuggestion.value; searchSuggestions = []; onsubmit?.(Object.assign(e, { input: elemValue, value: value })); }}>{searchSuggestion.name}</button > </li> {/each} </ul> {/if} </div> <!-- close the search suggestions box when clicking outside --> {#if elemValue && searchSuggestions.length !== 0} <button aria-label=" " class="absolute top-0 left-0 z-10 w-full h-full cursor-default" onclick={() => (searchSuggestions = [])} ></button> {/if}