flotilla/src/lib/components/InputList.svelte

93 lines
2.6 KiB
Svelte
Raw Normal View History

<script lang="ts">
import type {Snippet} from "svelte"
2026-07-15 16:30:17 +00:00
import type {Maybe} from "@welshman/lib"
import {append, removeAt, replaceAt, insertAt} from "@welshman/lib"
import Button from "@lib/components/Button.svelte"
2025-09-10 22:26:39 +00:00
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
import HamburgerMenu from "@assets/icons/hamburger-menu.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
type Props = {
value: string[]
addLabel?: Snippet
placeholder?: string
}
let {
value = $bindable(),
addLabel,
placeholder = "Enter text...",
allowAdd = true,
}: Props & {allowAdd?: boolean} = $props()
2026-07-15 16:30:17 +00:00
let draggedIndex: Maybe<number> = $state()
const onChange = (newValue: string[]) => {
value = newValue
}
const addItem = () => onChange(append("", value))
const removeItem = (index: number) => onChange(removeAt(index, value))
const updateItem = (index: number, item: string) => onChange(replaceAt(index, item, value))
const handleDragStart = (e: DragEvent, index: number) => {
draggedIndex = index
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = "move"
}
}
const handleDragOver = (e: DragEvent, index: number) => {
e.preventDefault()
2026-07-15 16:30:17 +00:00
if (draggedIndex !== undefined && draggedIndex !== index) {
onChange(insertAt(index, value[draggedIndex], removeAt(draggedIndex, value)))
draggedIndex = index
}
}
const handleDragEnd = () => {
2026-07-15 16:30:17 +00:00
draggedIndex = undefined
}
</script>
<div class="flex flex-col gap-2" role="list">
2026-08-22 17:32:51 +00:00
<!-- eslint-disable-next-line svelte/require-each-key -->
2025-09-22 18:06:16 +00:00
{#each value as item, index}
<div
class="flex items-center gap-2"
draggable="true"
role="listitem"
aria-label="Draggable item"
ondragstart={e => handleDragStart(e, index)}
ondragover={e => handleDragOver(e, index)}
ondragend={handleDragEnd}>
<Button onclick={() => removeItem(index)}>
2025-09-10 22:26:39 +00:00
<Icon icon={TrashBin2} />
</Button>
<input
type="text"
class="input w-full"
value={item}
{placeholder}
oninput={e => updateItem(index, e.currentTarget.value)} />
<div class="cursor-move" role="button" aria-label="Drag handle">
2025-09-10 22:26:39 +00:00
<Icon icon={HamburgerMenu} />
</div>
</div>
{/each}
{#if allowAdd}
<Button class="button button-link w-fit px-0" onclick={addItem}>
<Icon icon={AddCircle} size={5} />
{#if addLabel}
{@render addLabel?.()}
{:else}
Add Item
{/if}
</Button>
{/if}
</div>