2022-03-03 00:42:43 +08:00
|
|
|
<script lang="ts">
|
2023-02-24 05:32:18 +08:00
|
|
|
import DropdownOptions from "./DropdownOptions.svelte";
|
2023-01-24 02:41:33 +08:00
|
|
|
import { createEventDispatcher } from "svelte";
|
2022-06-02 01:02:18 +08:00
|
|
|
import { BlockTitle } from "@gradio/atoms";
|
2023-02-24 05:32:18 +08:00
|
|
|
import { Remove, DropdownArrow } from "@gradio/icons";
|
2022-03-03 00:42:43 +08:00
|
|
|
export let label: string;
|
2023-02-23 07:16:15 +08:00
|
|
|
export let info: string | undefined = undefined;
|
2023-03-01 02:18:33 +08:00
|
|
|
export let value: string | Array<string> | undefined;
|
2023-01-05 08:13:46 +08:00
|
|
|
export let multiselect: boolean = false;
|
2023-02-24 05:32:18 +08:00
|
|
|
export let max_choices: number;
|
2022-03-03 00:42:43 +08:00
|
|
|
export let choices: Array<string>;
|
2022-04-08 03:36:49 +08:00
|
|
|
export let disabled: boolean = false;
|
2022-04-27 18:47:15 +08:00
|
|
|
export let show_label: boolean;
|
2023-01-24 02:41:33 +08:00
|
|
|
|
|
|
|
const dispatch = createEventDispatcher<{
|
2023-02-24 05:32:18 +08:00
|
|
|
change: string | Array<string> | undefined;
|
2023-01-24 02:41:33 +08:00
|
|
|
}>();
|
|
|
|
|
2023-02-24 05:32:18 +08:00
|
|
|
let inputValue: string,
|
|
|
|
activeOption: string,
|
|
|
|
showOptions = false;
|
|
|
|
|
|
|
|
$: filtered = choices.filter((o) =>
|
|
|
|
inputValue ? o.toLowerCase().includes(inputValue.toLowerCase()) : o
|
|
|
|
);
|
|
|
|
$: if (
|
|
|
|
(activeOption && !filtered.includes(activeOption)) ||
|
|
|
|
(!activeOption && inputValue)
|
|
|
|
)
|
|
|
|
activeOption = filtered[0];
|
|
|
|
|
|
|
|
$: readonly =
|
|
|
|
(!multiselect && typeof value === "string") ||
|
|
|
|
(multiselect && Array.isArray(value) && value.length === max_choices);
|
|
|
|
|
2023-02-23 23:55:43 +08:00
|
|
|
// The initial value of value is [] so that can
|
|
|
|
// cause infinite loops in the non-multiselect case
|
|
|
|
$: if (!multiselect && !Array.isArray(value)) {
|
2023-01-24 02:41:33 +08:00
|
|
|
dispatch("change", value);
|
|
|
|
}
|
2023-02-24 05:32:18 +08:00
|
|
|
|
|
|
|
function add(option: string) {
|
|
|
|
if (Array.isArray(value)) {
|
2023-03-01 02:18:33 +08:00
|
|
|
if (!max_choices || value.length < max_choices) {
|
2023-02-24 05:32:18 +08:00
|
|
|
value.push(option);
|
|
|
|
dispatch("change", value);
|
|
|
|
}
|
|
|
|
showOptions = !(value.length === max_choices);
|
|
|
|
}
|
|
|
|
value = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
function remove(option: string) {
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
value = value.filter((v: string) => v !== option);
|
|
|
|
dispatch("change", value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function remove_all(e: any) {
|
2023-03-01 02:18:33 +08:00
|
|
|
if (multiselect) {
|
|
|
|
value = [];
|
|
|
|
} else {
|
|
|
|
value = "";
|
|
|
|
}
|
|
|
|
|
2023-02-24 05:32:18 +08:00
|
|
|
inputValue = "";
|
|
|
|
e.preventDefault();
|
|
|
|
dispatch("change", value);
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleOptionMousedown(e: any) {
|
|
|
|
const option = e.detail.target.dataset.value;
|
|
|
|
inputValue = "";
|
|
|
|
|
|
|
|
if (option !== undefined) {
|
|
|
|
if (!multiselect) {
|
|
|
|
value = option;
|
|
|
|
inputValue = "";
|
2023-03-11 01:52:17 +08:00
|
|
|
showOptions = false;
|
2023-02-24 05:32:18 +08:00
|
|
|
dispatch("change", value);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (value?.includes(option)) {
|
|
|
|
remove(option);
|
|
|
|
} else {
|
|
|
|
add(option);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleKeyup(e: any) {
|
|
|
|
if (e.key === "Enter" && activeOption != undefined) {
|
|
|
|
if (!multiselect) {
|
|
|
|
value = activeOption;
|
|
|
|
inputValue = "";
|
|
|
|
} else if (multiselect && Array.isArray(value)) {
|
|
|
|
value.includes(activeOption) ? remove(activeOption) : add(activeOption);
|
|
|
|
inputValue = "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
|
|
|
const increment = e.key === "ArrowUp" ? -1 : 1;
|
|
|
|
const calcIndex = filtered.indexOf(activeOption) + increment;
|
|
|
|
activeOption =
|
|
|
|
calcIndex < 0
|
|
|
|
? filtered[filtered.length - 1]
|
|
|
|
: calcIndex === filtered.length
|
|
|
|
? filtered[0]
|
|
|
|
: filtered[calcIndex];
|
|
|
|
}
|
|
|
|
if (e.key === "Escape") {
|
|
|
|
showOptions = false;
|
|
|
|
}
|
|
|
|
}
|
2022-03-03 00:42:43 +08:00
|
|
|
</script>
|
|
|
|
|
2023-01-05 08:13:46 +08:00
|
|
|
<!-- svelte-ignore a11y-label-has-associated-control -->
|
2022-04-26 22:48:39 +08:00
|
|
|
<label>
|
2023-02-23 07:16:15 +08:00
|
|
|
<BlockTitle {show_label} {info}>{label}</BlockTitle>
|
2023-01-18 04:47:40 +08:00
|
|
|
|
2023-02-24 05:32:18 +08:00
|
|
|
<div class="wrap">
|
|
|
|
<div class="wrap-inner" class:showOptions>
|
|
|
|
{#if Array.isArray(value)}
|
|
|
|
{#each value as s}
|
|
|
|
<div on:click|preventDefault={() => remove(s)} class="token">
|
|
|
|
<span>{s}</span>
|
|
|
|
<div
|
|
|
|
class:hidden={disabled}
|
|
|
|
class="token-remove"
|
|
|
|
title="Remove {s}"
|
|
|
|
>
|
|
|
|
<Remove />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{/each}
|
|
|
|
{:else}
|
|
|
|
<span class="single-select">{value}</span>
|
|
|
|
{/if}
|
|
|
|
<div class="secondary-wrap">
|
|
|
|
<input
|
|
|
|
class="border-none"
|
|
|
|
{disabled}
|
|
|
|
{readonly}
|
|
|
|
autocomplete="off"
|
|
|
|
bind:value={inputValue}
|
|
|
|
on:focus={() =>
|
|
|
|
(showOptions =
|
|
|
|
Array.isArray(value) && value.length === max_choices
|
|
|
|
? false
|
|
|
|
: true)}
|
|
|
|
on:blur={() => (showOptions = false)}
|
|
|
|
on:keyup={handleKeyup}
|
|
|
|
/>
|
|
|
|
<div
|
|
|
|
class:hide={!value?.length || disabled}
|
|
|
|
class="token-remove remove-all"
|
|
|
|
title="Remove All"
|
|
|
|
on:click={remove_all}
|
|
|
|
>
|
|
|
|
<Remove />
|
|
|
|
</div>
|
|
|
|
<DropdownArrow />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<DropdownOptions
|
|
|
|
bind:value
|
|
|
|
{showOptions}
|
|
|
|
{filtered}
|
|
|
|
{activeOption}
|
|
|
|
{disabled}
|
|
|
|
on:change={handleOptionMousedown}
|
|
|
|
/>
|
|
|
|
</div>
|
2022-04-26 22:48:39 +08:00
|
|
|
</label>
|
2023-01-18 04:47:40 +08:00
|
|
|
|
|
|
|
<style>
|
2023-02-24 05:32:18 +08:00
|
|
|
.wrap {
|
2023-01-18 04:47:40 +08:00
|
|
|
position: relative;
|
2023-03-07 04:52:31 +08:00
|
|
|
box-shadow: var(--input-shadow);
|
|
|
|
border: var(--input-border-width) solid var(--color-border-primary);
|
|
|
|
border-radius: var(--input-radius);
|
|
|
|
background: var(--input-background);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
2023-02-24 05:32:18 +08:00
|
|
|
.wrap:focus-within {
|
2023-03-07 04:52:31 +08:00
|
|
|
box-shadow: var(--input-shadow-focus);
|
2023-01-18 04:47:40 +08:00
|
|
|
border-color: var(--input-border-color-focus);
|
|
|
|
}
|
|
|
|
|
2023-02-24 05:32:18 +08:00
|
|
|
.wrap-inner {
|
|
|
|
display: flex;
|
|
|
|
position: relative;
|
|
|
|
flex-wrap: wrap;
|
|
|
|
align-items: center;
|
2023-03-07 04:52:31 +08:00
|
|
|
gap: var(--checkbox-label-gap);
|
|
|
|
padding: var(--checkbox-label-gap);
|
2023-01-18 04:47:40 +08:00
|
|
|
}
|
|
|
|
|
2023-02-24 05:32:18 +08:00
|
|
|
.token {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
2023-03-07 04:52:31 +08:00
|
|
|
transition: var(--button-transition);
|
2023-02-24 05:32:18 +08:00
|
|
|
cursor: pointer;
|
2023-03-07 04:52:31 +08:00
|
|
|
box-shadow: var(--checkbox-label-shadow);
|
|
|
|
border: var(--checkbox-label-border-width) solid
|
|
|
|
var(--checkbox-label-border-color);
|
|
|
|
border-radius: var(--button-small-radius);
|
|
|
|
background: var(--checkbox-label-background);
|
|
|
|
padding: var(--checkbox-label-padding);
|
|
|
|
color: var(--checkbox-text-color);
|
|
|
|
font-weight: var(--checkbox-label-text-weight);
|
|
|
|
font-size: var(--checkbox-label-text-size);
|
2023-02-24 05:32:18 +08:00
|
|
|
line-height: var(--line-md);
|
|
|
|
}
|
|
|
|
|
|
|
|
.token > * + * {
|
|
|
|
margin-left: var(--size-2);
|
|
|
|
}
|
|
|
|
|
|
|
|
.token-remove {
|
2023-03-07 04:52:31 +08:00
|
|
|
fill: var(--body-text-color);
|
2023-02-24 05:32:18 +08:00
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
cursor: pointer;
|
2023-03-07 04:52:31 +08:00
|
|
|
border: var(--checkbox-border-width) solid var(--color-border-primary);
|
2023-02-24 05:32:18 +08:00
|
|
|
border-radius: var(--radius-full);
|
2023-03-07 04:52:31 +08:00
|
|
|
background: var(--color-background-primary);
|
2023-02-24 05:32:18 +08:00
|
|
|
padding: var(--size-0-5);
|
|
|
|
width: 18px;
|
|
|
|
height: 18px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.single-select {
|
2023-03-07 04:52:31 +08:00
|
|
|
margin: var(--spacing-sm);
|
|
|
|
color: var(--body-text-color);
|
2023-02-24 05:32:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
.secondary-wrap {
|
|
|
|
display: flex;
|
|
|
|
flex: 1 1 0%;
|
|
|
|
align-items: center;
|
|
|
|
border: none;
|
|
|
|
min-width: min-content;
|
|
|
|
}
|
|
|
|
|
|
|
|
input {
|
2023-03-07 04:52:31 +08:00
|
|
|
margin: var(--spacing-sm);
|
2023-02-24 05:32:18 +08:00
|
|
|
outline: none;
|
|
|
|
border: none;
|
|
|
|
background: inherit;
|
2023-03-07 04:52:31 +08:00
|
|
|
width: var(--size-full);
|
|
|
|
color: var(--body-text-color);
|
|
|
|
font-size: var(--input-text-size);
|
2023-02-24 05:32:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
input:disabled {
|
2023-01-18 04:47:40 +08:00
|
|
|
cursor: not-allowed;
|
|
|
|
}
|
2023-02-24 05:32:18 +08:00
|
|
|
|
|
|
|
.remove-all {
|
|
|
|
margin-left: var(--size-1);
|
|
|
|
width: 20px;
|
|
|
|
height: 20px;
|
|
|
|
}
|
2023-01-18 04:47:40 +08:00
|
|
|
</style>
|