{#if open}
{ if (e.key === 'Escape') { closePicker(); searchInput?.focus(); } }} onscroll={(e) => { if (!onscroll) return; const target = e.target as HTMLDivElement; if (!target) return; const margin = 10; // 10px margin for top & bottom const atTop = target.scrollTop < margin; const atBottom = target.scrollTop + target.clientHeight > target.scrollHeight - margin; onscroll({ event: e, top: atTop, bottom: atBottom, searchInput: searchInput?.value ?? '' }); }} tabindex="0" > {#each filteredItems as opt (opt.value)} {@render option(opt)} {:else} {#if loading} {@render option(loadingOption, true)} {:else} {@render option(notFoundOption, true)} {/if} {/each}
{/if}
{#if label} {/if} { valid = e.detail.valid; onvalidate?.(e); }} />
{@render searchInputBox(true)}
{#if invalidMessage !== null}
{/if}
{#snippet optionIcon(opt: ComboboxOption)} {#if iconRender} {@render iconRender(opt)} {:else if opt.icon} {/if} {/snippet} {#snippet option(opt: ComboboxOption, forceDisabled?: boolean)} {@const optDisabled = opt.disabled || forceDisabled}
{ if (optDisabled) return; updateValue(opt); searchInput?.focus(); }} onkeydown={() => {}} tabindex="-1" > {@render optionIcon(opt)}
{@render snippetOrString(opt, labelRender || getLabel(opt))}
{#if opt.infotext || infotextRender}
{@render snippetOrString(opt, infotextRender || opt.infotext)}
{/if} {#if value?.value === opt.value}
{/if}
{/snippet} {#snippet searchInputBox(caret: boolean = true)}
{#if iconVisible}
{#if loading} {:else if useHighlighted && highlighted} {@render optionIcon(highlighted)} {:else if value} {@render optionIcon(value)} {:else if icon} {#if typeof icon === 'function'} {@render icon()} {:else} {/if} {:else} ❌ {/if}
{/if} { if (!open) { setTimeout(() => { searchInput?.select(); }, 100); } openPicker(); }} onkeydown={(e) => { if (!searchInput) return; if (e.key === 'Tab' || e.key === 'Enter') { if (open && highlighted && !highlighted.disabled && highlighted.value !== value?.value) { updateValue(highlighted); } if (e.key === 'Enter') { closePicker(); e.preventDefault(); } return; } else if (e.key === 'Escape') { closePicker(); return; } // open the picker openPicker(); if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { searching = false; e.preventDefault(); } if (e.key === 'ArrowDown') { highlighted = getNextOption(); return; } else if (e.key === 'ArrowUp') { highlighted = getPrevOption(); return; } }} oninput={() => { if (!searchInput) return; searchValue = searchInput.value; searching = true; onsearch?.(searchValue); }} use={() => { conditionalUse(); }} /> {#if (value && value.infotext) || (highlighted && useHighlighted && highlighted.infotext) || infotextRender}
{#if useHighlighted && highlighted} {@render snippetOrString(highlighted, infotextRender || highlighted.infotext)} {:else if value} {@render snippetOrString(value, infotextRender || value.infotext)} {/if}
{/if} {#if caret} { open = !open; if (open) searchInput?.focus(); }} /> {/if}
{/snippet} {#snippet snippetOrString( opt: ComboboxOption, value: string | Snippet<[item: ComboboxOption]> | undefined )} {#if typeof value === 'function'} {@render value(opt)} {:else} {value} {/if} {/snippet}