paste/src/components/ComboBox.svelte
2023-10-31 07:05:32 +01:00

91 lines
2.0 KiB
Svelte

<script lang="ts">
import { clickoutside } from '@svelte-put/clickoutside'
import { onMount, tick } from 'svelte'
type Item = { text: string }
export let value: Item | null = null
export let items: Item[]
let filteredItems: Item[] = []
let inputFilter: HTMLInputElement | null = null
let filter = ''
let divContainer: HTMLDivElement | null = null
let areOptionsVisible = false
$: {
if (filter) {
filteredItems = items.filter((item) => item.text.toLowerCase().includes(filter.toLowerCase()))
} else {
filteredItems = [...items]
}
}
$: {
// Watch filter value
if (filter) {
tick().then(() => {
if (divContainer) {
if (divContainer.scrollHeight > divContainer.clientHeight) {
divContainer.classList.add('h-96')
} else {
divContainer.classList.remove('h-96')
}
}
})
}
}
function showOptions() {
// Add a delay so that the click outside event doesn't trigger too soon
setTimeout(() => {
areOptionsVisible = true
tick().then(() => inputFilter?.select())
}, 0)
setTimeout(() => {}, 100)
}
function hideOptions() {
areOptionsVisible = false
}
function selectItem(item: Item) {
value = item
hideOptions()
}
</script>
<div class="relative" use:clickoutside on:clickoutside={hideOptions}>
{#if areOptionsVisible}
<input
type="text"
bind:value={filter}
bind:this={inputFilter}
class={$$restProps.class ?? ''}
/>
<div
class="absolute top-full left-0 w-full bg-gray-700 max-h-[90vh] overflow-y-auto"
bind:this={divContainer}
>
{#each filteredItems as item}
<div class="p-1 hover:bg-gray-600" on:click={() => selectItem(item)}>{item.text}</div>
{/each}
</div>
{:else}
<input
type="text"
readonly
value={value?.text ?? ''}
class={$$restProps.class ?? ''}
on:click={showOptions}
/>
{/if}
</div>
<style lang="scss">
select {
appearance: none;
}
</style>