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-05-16 09:36:57 +08:00
|
|
|
import { createEventDispatcher, afterUpdate } 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";
|
2023-03-14 08:12:41 +08:00
|
|
|
import type { SelectData } from "@gradio/utils";
|
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-07-31 20:31:39 +08:00
|
|
|
export let value: string | string[] | undefined;
|
2023-05-12 05:28:19 +08:00
|
|
|
let old_value = Array.isArray(value) ? value.slice() : value;
|
2023-07-31 20:31:39 +08:00
|
|
|
export let value_is_output = false;
|
|
|
|
export let multiselect = false;
|
2023-02-24 05:32:18 +08:00
|
|
|
export let max_choices: number;
|
2023-07-31 20:31:39 +08:00
|
|
|
export let choices: string[];
|
|
|
|
export let disabled = false;
|
2022-04-27 18:47:15 +08:00
|
|
|
export let show_label: boolean;
|
2023-07-31 20:31:39 +08:00
|
|
|
export let container = true;
|
|
|
|
export let allow_custom_value = false;
|
2023-03-31 02:20:34 +08:00
|
|
|
|
2023-01-24 02:41:33 +08:00
|
|
|
const dispatch = createEventDispatcher<{
|
2023-07-31 20:31:39 +08:00
|
|
|
change: string | string[] | undefined;
|
2023-05-16 09:36:57 +08:00
|
|
|
input: undefined;
|
2023-03-14 08:12:41 +08:00
|
|
|
select: SelectData;
|
2023-03-31 02:20:34 +08:00
|
|
|
blur: undefined;
|
2023-08-02 04:02:50 +08:00
|
|
|
focus: undefined;
|
2023-01-24 02:41:33 +08:00
|
|
|
}>();
|
|
|
|
|
2023-06-14 08:18:14 +08:00
|
|
|
let inputValue: string | undefined,
|
2023-04-28 00:56:39 +08:00
|
|
|
activeOption: string | null,
|
|
|
|
showOptions = false,
|
|
|
|
filterInput: HTMLElement;
|
2023-02-24 05:32:18 +08:00
|
|
|
|
2023-06-14 08:18:14 +08:00
|
|
|
$: if (typeof value === "string" || value === null) {
|
2023-04-29 02:58:22 +08:00
|
|
|
inputValue = value;
|
|
|
|
}
|
|
|
|
|
2023-02-24 05:32:18 +08:00
|
|
|
$: filtered = choices.filter((o) =>
|
|
|
|
inputValue ? o.toLowerCase().includes(inputValue.toLowerCase()) : o
|
|
|
|
);
|
|
|
|
|
2023-04-28 00:56:39 +08:00
|
|
|
$: if (!activeOption || !filtered.includes(activeOption)) {
|
|
|
|
activeOption = filtered.length ? filtered[0] : null;
|
2023-01-24 02:41:33 +08:00
|
|
|
}
|
2023-02-24 05:32:18 +08:00
|
|
|
|
2023-07-31 20:31:39 +08:00
|
|
|
function handle_change(): void {
|
2023-05-16 09:36:57 +08:00
|
|
|
dispatch("change", value);
|
|
|
|
if (!value_is_output) {
|
|
|
|
dispatch("input");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
afterUpdate(() => {
|
|
|
|
value_is_output = false;
|
|
|
|
});
|
|
|
|
$: {
|
|
|
|
if (JSON.stringify(value) != JSON.stringify(old_value)) {
|
|
|
|
old_value = Array.isArray(value) ? value.slice() : value;
|
|
|
|
handle_change();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-31 20:31:39 +08:00
|
|
|
function add(option: string): void {
|
|
|
|
value = value as string[];
|
2023-04-28 00:56:39 +08:00
|
|
|
if (!max_choices || value.length < max_choices) {
|
|
|
|
value.push(option);
|
2023-03-14 08:12:41 +08:00
|
|
|
dispatch("select", {
|
|
|
|
index: choices.indexOf(option),
|
|
|
|
value: option,
|
2023-04-28 00:56:39 +08:00
|
|
|
selected: true
|
2023-03-14 08:12:41 +08:00
|
|
|
});
|
2023-02-24 05:32:18 +08:00
|
|
|
}
|
2023-04-28 00:56:39 +08:00
|
|
|
value = value;
|
2023-02-24 05:32:18 +08:00
|
|
|
}
|
|
|
|
|
2023-07-31 20:31:39 +08:00
|
|
|
function remove(option: string): void {
|
|
|
|
value = value as string[];
|
2023-04-28 00:56:39 +08:00
|
|
|
value = value.filter((v: string) => v !== option);
|
|
|
|
dispatch("select", {
|
|
|
|
index: choices.indexOf(option),
|
|
|
|
value: option,
|
|
|
|
selected: false
|
|
|
|
});
|
|
|
|
}
|
2023-03-01 02:18:33 +08:00
|
|
|
|
2023-07-31 20:31:39 +08:00
|
|
|
function remove_all(e: any): void {
|
2023-04-28 00:56:39 +08:00
|
|
|
value = [];
|
2023-02-24 05:32:18 +08:00
|
|
|
inputValue = "";
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
|
2023-08-02 04:02:50 +08:00
|
|
|
function handle_blur(e: FocusEvent) {
|
|
|
|
if (multiselect) {
|
|
|
|
inputValue = "";
|
|
|
|
} else if (!allow_custom_value) {
|
|
|
|
if (value !== inputValue) {
|
|
|
|
if (typeof value === "string" && inputValue == "") {
|
|
|
|
inputValue = value;
|
|
|
|
} else {
|
|
|
|
value = undefined;
|
|
|
|
inputValue = "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
showOptions = false;
|
|
|
|
dispatch("blur");
|
|
|
|
}
|
|
|
|
|
|
|
|
function handle_focus(e: FocusEvent){
|
|
|
|
dispatch("focus");
|
|
|
|
showOptions = !showOptions;
|
|
|
|
if (showOptions) {
|
|
|
|
filtered = choices;
|
|
|
|
} else {
|
|
|
|
filterInput.blur();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-31 20:31:39 +08:00
|
|
|
function handleOptionMousedown(e: any): void {
|
2023-02-24 05:32:18 +08:00
|
|
|
const option = e.detail.target.dataset.value;
|
2023-03-31 07:38:08 +08:00
|
|
|
if (allow_custom_value) {
|
|
|
|
inputValue = option;
|
|
|
|
}
|
2023-02-24 05:32:18 +08:00
|
|
|
|
|
|
|
if (option !== undefined) {
|
2023-04-28 00:56:39 +08:00
|
|
|
if (multiselect) {
|
|
|
|
if (value?.includes(option)) {
|
|
|
|
remove(option);
|
|
|
|
} else {
|
|
|
|
add(option);
|
|
|
|
}
|
|
|
|
inputValue = "";
|
|
|
|
} else {
|
2023-02-24 05:32:18 +08:00
|
|
|
value = option;
|
2023-04-28 00:56:39 +08:00
|
|
|
inputValue = option;
|
2023-03-11 01:52:17 +08:00
|
|
|
showOptions = false;
|
2023-03-14 08:12:41 +08:00
|
|
|
dispatch("select", {
|
|
|
|
index: choices.indexOf(option),
|
|
|
|
value: option,
|
|
|
|
selected: true
|
|
|
|
});
|
2023-02-24 05:32:18 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-28 00:56:39 +08:00
|
|
|
function handleKeydown(e: any) {
|
2023-02-24 05:32:18 +08:00
|
|
|
if (e.key === "Enter" && activeOption != undefined) {
|
|
|
|
if (!multiselect) {
|
2023-04-28 00:56:39 +08:00
|
|
|
if (value !== activeOption) {
|
|
|
|
value = activeOption;
|
|
|
|
dispatch("select", {
|
|
|
|
index: choices.indexOf(value),
|
|
|
|
value: value,
|
|
|
|
selected: true
|
|
|
|
});
|
|
|
|
}
|
|
|
|
inputValue = activeOption;
|
|
|
|
showOptions = false;
|
2023-02-24 05:32:18 +08:00
|
|
|
} else if (multiselect && Array.isArray(value)) {
|
|
|
|
value.includes(activeOption) ? remove(activeOption) : add(activeOption);
|
|
|
|
inputValue = "";
|
|
|
|
}
|
2023-04-28 00:56:39 +08:00
|
|
|
} else {
|
|
|
|
showOptions = true;
|
|
|
|
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
|
|
|
if (activeOption === null) {
|
|
|
|
activeOption = filtered[0];
|
|
|
|
}
|
|
|
|
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];
|
|
|
|
e.preventDefault();
|
|
|
|
} else if (e.key === "Escape") {
|
|
|
|
showOptions = false;
|
|
|
|
} else if (e.key === "Backspace") {
|
|
|
|
if (
|
|
|
|
multiselect &&
|
|
|
|
(!inputValue || inputValue === "") &&
|
|
|
|
Array.isArray(value) &&
|
|
|
|
value.length > 0
|
|
|
|
) {
|
|
|
|
remove(value[value.length - 1]);
|
|
|
|
inputValue = "";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
showOptions = true;
|
|
|
|
}
|
2023-02-24 05:32:18 +08:00
|
|
|
}
|
|
|
|
}
|
2023-05-12 05:28:19 +08:00
|
|
|
|
|
|
|
$: {
|
|
|
|
if (JSON.stringify(value) != JSON.stringify(old_value)) {
|
|
|
|
dispatch("change", value);
|
|
|
|
old_value = Array.isArray(value) ? value.slice() : value;
|
|
|
|
}
|
|
|
|
}
|
2022-03-03 00:42:43 +08:00
|
|
|
</script>
|
|
|
|
|
2023-07-18 01:05:46 +08:00
|
|
|
<label class:container>
|
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>
|
2023-04-28 00:56:39 +08:00
|
|
|
{#if multiselect && Array.isArray(value)}
|
2023-02-24 05:32:18 +08:00
|
|
|
{#each value as s}
|
2023-04-28 00:56:39 +08:00
|
|
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
2023-02-24 05:32:18 +08:00
|
|
|
<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}
|
|
|
|
{/if}
|
|
|
|
<div class="secondary-wrap">
|
|
|
|
<input
|
|
|
|
class="border-none"
|
2023-04-28 00:56:39 +08:00
|
|
|
class:subdued={value !== inputValue && !allow_custom_value}
|
2023-02-24 05:32:18 +08:00
|
|
|
{disabled}
|
|
|
|
autocomplete="off"
|
|
|
|
bind:value={inputValue}
|
2023-04-28 00:56:39 +08:00
|
|
|
bind:this={filterInput}
|
|
|
|
on:keydown={handleKeydown}
|
|
|
|
on:keyup={() => {
|
|
|
|
if (allow_custom_value) {
|
|
|
|
value = inputValue;
|
|
|
|
}
|
2023-03-31 02:20:34 +08:00
|
|
|
}}
|
2023-08-02 04:02:50 +08:00
|
|
|
on:blur={handle_blur}
|
|
|
|
on:focus={handle_focus}
|
2023-02-24 05:32:18 +08:00
|
|
|
/>
|
2023-04-28 00:56:39 +08:00
|
|
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
2023-02-24 05:32:18 +08:00
|
|
|
<div
|
2023-03-29 10:21:51 +08:00
|
|
|
class:hide={!multiselect || !value?.length || disabled}
|
2023-02-24 05:32:18 +08:00
|
|
|
class="token-remove remove-all"
|
2023-04-28 00:56:39 +08:00
|
|
|
title="Clear"
|
2023-02-24 05:32:18 +08:00
|
|
|
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-07-18 01:05:46 +08:00
|
|
|
label:not(.container), label:not(.container) .wrap, label:not(.container) .wrap-inner, label:not(.container) .secondary-wrap, label:not(.container) .token, label:not(.container) input {
|
|
|
|
height: 100%;
|
|
|
|
}
|
|
|
|
.container .wrap {
|
2023-03-07 04:52:31 +08:00
|
|
|
box-shadow: var(--input-shadow);
|
2023-03-17 22:41:53 +08:00
|
|
|
border: var(--input-border-width) solid var(--border-color-primary);
|
2023-07-18 01:05:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
.wrap {
|
|
|
|
position: relative;
|
2023-03-07 04:52:31 +08:00
|
|
|
border-radius: var(--input-radius);
|
2023-03-18 08:20:55 +08:00
|
|
|
background: var(--input-background-fill);
|
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);
|
2023-04-28 00:56:39 +08:00
|
|
|
padding: var(--checkbox-label-padding);
|
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);
|
2023-03-18 08:20:55 +08:00
|
|
|
background: var(--checkbox-label-background-fill);
|
2023-03-07 04:52:31 +08:00
|
|
|
padding: var(--checkbox-label-padding);
|
2023-03-18 08:20:55 +08:00
|
|
|
color: var(--checkbox-label-text-color);
|
2023-03-07 04:52:31 +08:00
|
|
|
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-17 22:41:53 +08:00
|
|
|
border: var(--checkbox-border-width) solid var(--border-color-primary);
|
2023-02-24 05:32:18 +08:00
|
|
|
border-radius: var(--radius-full);
|
2023-03-18 08:20:55 +08:00
|
|
|
background: var(--background-fill-primary);
|
2023-02-24 05:32:18 +08:00
|
|
|
padding: var(--size-0-5);
|
|
|
|
width: 18px;
|
|
|
|
height: 18px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.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-04-28 22:53:44 +08:00
|
|
|
-webkit-text-fill-color: var(--body-text-color);
|
|
|
|
-webkit-opacity: 1;
|
|
|
|
opacity: 1;
|
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-03-29 10:21:51 +08:00
|
|
|
|
|
|
|
.hide {
|
|
|
|
display: none;
|
|
|
|
}
|
2023-04-28 00:56:39 +08:00
|
|
|
|
|
|
|
.subdued {
|
|
|
|
color: var(--body-text-color-subdued);
|
|
|
|
}
|
2023-01-18 04:47:40 +08:00
|
|
|
</style>
|