You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

180 lines
5.8 KiB
Svelte

<script context="module">import { fly, scale } from "svelte/transition";
import { prefersReducedMotionStore } from "../../index.js";
import { dynamicTransition } from "../../internal/transitions.js";
</script>
<script
generics="ListTransitionIn extends Transition = FlyTransition, ListTransitionOut extends Transition = FlyTransition, ChipTransitionIn extends Transition = ScaleTransition, ChipTransitionOut extends Transition = ScaleTransition"
>import { createEventDispatcher, onMount } from "svelte";
import { flip } from "svelte/animate";
const dispatch = createEventDispatcher();
export let input = "";
export let name;
export let value = [];
export let whitelist = [];
export let max = -1;
export let minlength = -1;
export let maxlength = -1;
export let allowUpperCase = false;
export let allowDuplicates = false;
export let validation = () => true;
export let duration = 150;
export let required = false;
export let chips = "variant-filled";
export let invalid = "input-error";
export let padding = "p-2";
export let rounded = "rounded-container-token";
export let regionChipWrapper = "";
export let regionChipList = "";
export let regionInput = "";
export let transitions = !$prefersReducedMotionStore;
export let listTransitionIn = fly;
export let listTransitionInParams = { duration: 150, opacity: 0, y: -20 };
export let listTransitionOut = fly;
export let listTransitionOutParams = { duration: 150, opacity: 0, y: -20 };
export let chipTransitionIn = scale;
export let chipTransitionInParams = { duration: 150, opacity: 0 };
export let chipTransitionOut = scale;
export let chipTransitionOutParams = { duration: 150, opacity: 0 };
const cBase = "textarea cursor-pointer";
const cChipWrapper = "space-y-4";
const cChipList = "flex flex-wrap gap-2";
const cInputField = "unstyled bg-transparent border-0 !ring-0 p-0 w-full";
let inputValid = true;
let chipValues = value?.map((val) => {
return { val, id: Math.random() };
}) || [];
function resetFormHandler() {
value = [];
}
let selectElement;
onMount(() => {
if (!selectElement.form)
return;
const externalForm = selectElement.form;
externalForm.addEventListener("reset", resetFormHandler);
return () => {
externalForm.removeEventListener("reset", resetFormHandler);
};
});
function onInputHandler() {
inputValid = true;
}
function validate() {
if (!input)
return false;
input = input.trim();
if (validation !== void 0 && !validation(input))
return false;
if (max !== -1 && value.length >= max)
return false;
if (minlength !== -1 && input.length < minlength)
return false;
if (maxlength !== -1 && input.length > maxlength)
return false;
if (whitelist.length > 0 && !whitelist.includes(input))
return false;
if (allowDuplicates === false && value.includes(input))
return false;
return true;
}
function addChip(event) {
event.preventDefault();
inputValid = validate();
if (inputValid === false) {
dispatch("invalid", { event, input });
return;
}
input = allowUpperCase ? input : input.toLowerCase();
value.push(input);
value = value;
chipValues.push({ val: input, id: Math.random() });
chipValues = chipValues;
dispatch("add", { event, chipIndex: value.length - 1, chipValue: input });
input = "";
}
function removeChip(event, chipIndex, chipValue) {
if ($$restProps.disabled)
return;
value.splice(chipIndex, 1);
value = value;
chipValues.splice(chipIndex, 1);
chipValues = chipValues;
dispatch("remove", { event, chipIndex, chipValue });
}
$:
classesInvalid = inputValid === false ? invalid : "";
$:
classesBase = `${cBase} ${padding} ${rounded} ${$$props.class ?? ""} ${classesInvalid}`;
$:
classesChipWrapper = `${cChipWrapper} ${regionChipWrapper}`;
$:
classesChipList = `${cChipList} ${regionChipList}`;
$:
classesInput = `${cInputField} ${regionInput}`;
$:
chipValues = value?.map((val, i) => {
if (chipValues[i]?.val === val)
return chipValues[i];
return { id: Math.random(), val };
}) || [];
</script>
<div class="input-chip {classesBase}" class:opacity-50={$$restProps.disabled}>
<!-- NOTE: Don't use `hidden` as it prevents `required` from operating -->
<div class="h-0 overflow-hidden">
<select bind:this={selectElement} bind:value {name} multiple {required} tabindex="-1">
<!-- NOTE: options are required! -->
{#each value as option}<option value={option}>{option}</option>{/each}
</select>
</div>
<!-- Chip Wrapper -->
<div class="input-chip-wrapper {classesChipWrapper}">
<!-- Input Field -->
<form on:submit={addChip}>
<input
type="text"
bind:value={input}
placeholder={$$restProps.placeholder ?? 'Enter values...'}
class="input-chip-field {classesInput}"
on:input={onInputHandler}
on:input
on:focus
on:blur
disabled={$$restProps.disabled}
/>
</form>
<!-- Chip List -->
{#if chipValues.length}
<div
class="input-chip-list {classesChipList}"
in:dynamicTransition|local={{ transition: listTransitionIn, params: listTransitionInParams, enabled: transitions }}
out:dynamicTransition|local={{ transition: listTransitionOut, params: listTransitionOutParams, enabled: transitions }}
>
{#each chipValues as { id, val }, i (id)}
<!-- Wrapping div required for FLIP animation -->
<div animate:flip={{ duration }}>
<button
type="button"
class="chip {chips}"
on:click={(e) => {
removeChip(e, i, val);
}}
on:click
on:keypress
on:keydown
on:keyup
in:dynamicTransition|local={{ transition: chipTransitionIn, params: chipTransitionInParams, enabled: transitions }}
out:dynamicTransition|local={{ transition: chipTransitionOut, params: chipTransitionOutParams, enabled: transitions }}
>
<span>{val}</span>
<span></span>
</button>
</div>
{/each}
</div>
{/if}
</div>
</div>