Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
Svelte
at icy/tmrrpn
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146<script lang="ts"> // inputField and InputTag live in ./inputField, which owns what both renderings read; // re-exporting them from here trips eslint's no-import-assign on module scripts import type { Component, Snippet } from "svelte"; import type { HTMLInputAttributes, SvelteHTMLElements } from "svelte/elements"; import { inputField, type InputFieldVariants, type InputTag, type TagCommitKey } from "./inputField"; import Spinner from "./Spinner.svelte"; import TagInput from "./TagInput.svelte";
interface Props extends Omit<HTMLInputAttributes, "class" | "value" | "size" | "list"> { value?: string; size?: InputFieldVariants["size"]; error?: boolean; disabled?: boolean; loading?: boolean; iconLeft?: Component<SvelteHTMLElements["svg"]>; iconRight?: Component<SvelteHTMLElements["svg"]>; suffix?: string; element?: HTMLInputElement; class?: string; /** Turn the field into a token editor: it still takes a draft, but what's committed lands * as a removable chip beside the caret. Same box, same states — reach for it once one * value stops being enough. */ rich?: boolean; // rich only /** two-way; the tokens the field holds. A bare `value` is a tag, a `field` makes it a filter. */ tags?: InputTag[]; /** hard ceiling on tags; native `max` still reaches the plain input */ maxTags?: number; /** what commits the draft: Enter, a typed space, a typed comma, or any mix. Defaults to * Enter alone. Whatever's in here splits a paste too. */ commitOn?: TagCommitKey[]; allowDuplicates?: boolean; /** vet a candidate before it lands; return a string to say why it didn't */ validate?: (value: string) => boolean | string; /** typeahead source. Anything already added is dropped from the list. */ suggestions?: InputTag[]; /** take filtering over, e.g. to query a server: `suggestions` is then rendered as given */ onSearch?: (query: string) => void; onAdd?: (tag: InputTag) => void; onRemove?: (tag: InputTag) => void; /** persistent helper text; a rejection cross-fades over it */ hint?: string; emptyLabel?: string; label?: string; panelClass?: string; /** the body of a chip — this is where a User, a ColorBall, an avatar stack goes */ tag?: Snippet<[InputTag]>; /** the body of a suggestion row; defaults to the chip's rendering */ suggestion?: Snippet<[InputTag]>; }
let { value = $bindable(""), size = "regular", error = false, disabled = false, loading = false, iconLeft, iconRight, suffix, element = $bindable(), class: className, rich = false, tags = $bindable([]), maxTags, commitOn, allowDuplicates, validate, suggestions, onSearch, onAdd, onRemove, hint, emptyLabel, label, panelClass, tag, suggestion, ...rest }: Props = $props();
const classes = $derived(inputField({ size, error, disabled, class: className })); const iconSize = $derived(size === "large" ? "size-5" : "size-4");</script>
{#if rich} <TagInput bind:tags bind:value bind:element {size} {error} {disabled} {loading} {iconLeft} {maxTags} {commitOn} {allowDuplicates} {validate} {suggestions} {onSearch} {onAdd} {onRemove} {hint} {emptyLabel} {label} {panelClass} {tag} {suggestion} class={className} {...rest} />{:else} <div class={classes}> {#if iconLeft} {@const IconLeft = iconLeft} <IconLeft class="{iconSize} shrink-0 text-foreground-subtle" aria-hidden="true" /> {/if} <input bind:this={element} bind:value {disabled} aria-invalid={error} aria-busy={loading} aria-label={label} class="mt-px min-w-0 flex-1 bg-transparent outline-none placeholder:text-foreground-placeholder disabled:cursor-not-allowed" {...rest} /> {#if suffix} <span class="shrink-0 text-foreground-muted select-none">{suffix}</span> {/if} {#if loading} <Spinner class="{iconSize} shrink-0" /> {:else if iconRight} {@const IconRight = iconRight} <IconRight class="{iconSize} shrink-0 text-foreground-subtle" aria-hidden="true" /> {/if} </div>{/if}