2022-03-03 00:42:43 +08:00
|
|
|
<script lang="ts">
|
2023-09-13 00:58:18 +08:00
|
|
|
import { afterUpdate, createEventDispatcher } from "svelte";
|
2023-08-18 19:36:52 +08:00
|
|
|
import { _ } from "svelte-i18n";
|
2023-09-13 00:58:18 +08:00
|
|
|
import type { SelectData } from "@gradio/utils";
|
|
|
|
import { BlockTitle } from "@gradio/atoms";
|
|
|
|
import { DropdownArrow } from "@gradio/icons";
|
|
|
|
import DropdownOptions from "./DropdownOptions.svelte";
|
|
|
|
import { handle_filter, handle_change, handle_shared_keys } from "./utils";
|
2023-08-18 19:36:52 +08:00
|
|
|
|
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-09-13 00:58:18 +08:00
|
|
|
export let value: string | number | (string | number)[] | undefined = [];
|
|
|
|
let old_value: string | number | (string | number)[] | undefined = [];
|
2023-07-31 20:31:39 +08:00
|
|
|
export let value_is_output = false;
|
2023-09-13 00:58:18 +08:00
|
|
|
export let choices: [string, string | number][];
|
|
|
|
let old_choices: [string, string | number][];
|
2023-07-31 20:31:39 +08:00
|
|
|
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-09-13 09:36:29 +08:00
|
|
|
export let filterable = true;
|
2023-03-31 02:20:34 +08:00
|
|
|
|
2023-09-13 00:58:18 +08:00
|
|
|
let filter_input: HTMLElement;
|
|
|
|
|
|
|
|
let show_options = false;
|
|
|
|
let choices_names: string[];
|
|
|
|
let choices_values: (string | number)[];
|
|
|
|
let input_text = "";
|
|
|
|
let old_input_text = "";
|
|
|
|
let initialized = false;
|
|
|
|
|
|
|
|
// All of these are indices with respect to the choices array
|
|
|
|
let filtered_indices: number[] = [];
|
|
|
|
let active_index: number | null = null;
|
|
|
|
// selected_index is null if allow_custom_value is true and the input_text is not in choices_names
|
|
|
|
let selected_index: number | null = null;
|
|
|
|
let old_selected_index: number | null;
|
|
|
|
|
2023-01-24 02:41:33 +08:00
|
|
|
const dispatch = createEventDispatcher<{
|
2023-09-13 00:58:18 +08:00
|
|
|
change: 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-09-13 00:58:18 +08:00
|
|
|
// Setting the initial value of the dropdown
|
|
|
|
if (value) {
|
|
|
|
old_selected_index = choices.map((c) => c[1]).indexOf(value as string);
|
|
|
|
selected_index = old_selected_index;
|
|
|
|
if (selected_index === -1) {
|
|
|
|
old_value = value;
|
|
|
|
selected_index = null;
|
|
|
|
} else {
|
|
|
|
[input_text, old_value] = choices[selected_index];
|
|
|
|
old_input_text = input_text;
|
2023-05-16 09:36:57 +08:00
|
|
|
}
|
2023-09-13 00:58:18 +08:00
|
|
|
} else if (choices.length > 0) {
|
|
|
|
old_selected_index = 0;
|
|
|
|
selected_index = 0;
|
|
|
|
[input_text, value] = choices[selected_index];
|
|
|
|
old_value = value;
|
|
|
|
old_input_text = input_text;
|
2023-05-16 09:36:57 +08:00
|
|
|
}
|
2023-08-25 01:22:41 +08:00
|
|
|
|
2023-05-16 09:36:57 +08:00
|
|
|
$: {
|
2023-09-13 00:58:18 +08:00
|
|
|
if (
|
|
|
|
selected_index !== old_selected_index &&
|
|
|
|
selected_index !== null &&
|
|
|
|
initialized
|
|
|
|
) {
|
|
|
|
[input_text, value] = choices[selected_index];
|
|
|
|
old_selected_index = selected_index;
|
2023-03-14 08:12:41 +08:00
|
|
|
dispatch("select", {
|
2023-09-13 00:58:18 +08:00
|
|
|
index: selected_index,
|
|
|
|
value: choices_values[selected_index],
|
2023-09-14 15:03:59 +08:00
|
|
|
selected: true
|
2023-03-14 08:12:41 +08:00
|
|
|
});
|
2023-02-24 05:32:18 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-13 00:58:18 +08:00
|
|
|
$: {
|
|
|
|
if (value != old_value) {
|
|
|
|
set_input_text();
|
|
|
|
handle_change(dispatch, value, value_is_output);
|
|
|
|
old_value = value;
|
2023-08-03 00:32:45 +08:00
|
|
|
}
|
2023-04-28 00:56:39 +08:00
|
|
|
}
|
2023-03-01 02:18:33 +08:00
|
|
|
|
2023-09-13 00:58:18 +08:00
|
|
|
$: {
|
|
|
|
choices_names = choices.map((c) => c[0]);
|
|
|
|
choices_values = choices.map((c) => c[1]);
|
2023-02-24 05:32:18 +08:00
|
|
|
}
|
|
|
|
|
2023-09-13 00:58:18 +08:00
|
|
|
$: {
|
|
|
|
if (choices !== old_choices || input_text !== old_input_text) {
|
|
|
|
filtered_indices = handle_filter(choices, input_text);
|
|
|
|
old_choices = choices;
|
|
|
|
old_input_text = input_text;
|
2023-09-14 15:03:59 +08:00
|
|
|
if (!allow_custom_value && filtered_indices.length > 0) {
|
2023-09-13 00:58:18 +08:00
|
|
|
active_index = filtered_indices[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-08-30 18:11:01 +08:00
|
|
|
|
2023-09-13 00:58:18 +08:00
|
|
|
function set_input_text(): void {
|
|
|
|
if (value === undefined) {
|
|
|
|
input_text = "";
|
|
|
|
} else if (choices_values.includes(value as string)) {
|
|
|
|
input_text = choices_names[choices_values.indexOf(value as string)];
|
|
|
|
} else if (allow_custom_value) {
|
|
|
|
input_text = value as string;
|
|
|
|
} else {
|
|
|
|
input_text = "";
|
2023-08-02 04:02:50 +08:00
|
|
|
}
|
2023-09-13 00:58:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function handle_option_selected(e: any): void {
|
|
|
|
selected_index = parseInt(e.detail.target.dataset.index);
|
2023-10-09 09:34:05 +08:00
|
|
|
if (isNaN(selected_index)) {
|
|
|
|
// This is the case when the user clicks on the scrollbar
|
|
|
|
selected_index = null;
|
|
|
|
return;
|
|
|
|
}
|
2023-09-13 00:58:18 +08:00
|
|
|
show_options = false;
|
|
|
|
active_index = null;
|
|
|
|
filter_input.blur();
|
2023-08-02 04:02:50 +08:00
|
|
|
}
|
|
|
|
|
2023-08-04 06:01:18 +08:00
|
|
|
function handle_focus(e: FocusEvent): void {
|
2023-09-13 00:58:18 +08:00
|
|
|
filtered_indices = choices.map((_, i) => i);
|
|
|
|
show_options = true;
|
2023-08-02 04:02:50 +08:00
|
|
|
dispatch("focus");
|
|
|
|
}
|
|
|
|
|
2023-09-13 00:58:18 +08:00
|
|
|
function handle_blur(): void {
|
|
|
|
if (!allow_custom_value) {
|
|
|
|
input_text = choices_names[choices_values.indexOf(value as string)];
|
2023-02-24 05:32:18 +08:00
|
|
|
}
|
2023-09-14 03:42:30 +08:00
|
|
|
value = input_text;
|
2023-09-13 00:58:18 +08:00
|
|
|
show_options = false;
|
|
|
|
active_index = null;
|
|
|
|
dispatch("blur");
|
2023-02-24 05:32:18 +08:00
|
|
|
}
|
|
|
|
|
2023-09-13 00:58:18 +08:00
|
|
|
function handle_key_down(e: KeyboardEvent): void {
|
|
|
|
[show_options, active_index] = handle_shared_keys(
|
|
|
|
e,
|
|
|
|
active_index,
|
|
|
|
filtered_indices
|
|
|
|
);
|
|
|
|
if (e.key === "Enter") {
|
|
|
|
if (active_index !== null) {
|
|
|
|
selected_index = active_index;
|
|
|
|
show_options = false;
|
|
|
|
filter_input.blur();
|
|
|
|
active_index = null;
|
|
|
|
} else if (choices_names.includes(input_text)) {
|
|
|
|
selected_index = choices_names.indexOf(input_text);
|
|
|
|
show_options = false;
|
|
|
|
active_index = null;
|
|
|
|
filter_input.blur();
|
|
|
|
} else if (allow_custom_value) {
|
|
|
|
value = input_text;
|
|
|
|
selected_index = null;
|
|
|
|
show_options = false;
|
|
|
|
active_index = null;
|
|
|
|
filter_input.blur();
|
2023-04-28 00:56:39 +08:00
|
|
|
}
|
2023-02-24 05:32:18 +08:00
|
|
|
}
|
|
|
|
}
|
2023-05-12 05:28:19 +08:00
|
|
|
|
2023-09-13 00:58:18 +08:00
|
|
|
afterUpdate(() => {
|
|
|
|
value_is_output = false;
|
|
|
|
initialized = true;
|
|
|
|
});
|
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">
|
2023-09-13 00:58:18 +08:00
|
|
|
<div class="wrap-inner" class:show_options>
|
2023-02-24 05:32:18 +08:00
|
|
|
<div class="secondary-wrap">
|
|
|
|
<input
|
|
|
|
class="border-none"
|
2023-09-13 00:58:18 +08:00
|
|
|
class:subdued={!choices_names.includes(input_text) &&
|
|
|
|
!allow_custom_value}
|
2023-02-24 05:32:18 +08:00
|
|
|
{disabled}
|
|
|
|
autocomplete="off"
|
2023-09-13 00:58:18 +08:00
|
|
|
bind:value={input_text}
|
|
|
|
bind:this={filter_input}
|
|
|
|
on:keydown={handle_key_down}
|
2023-08-02 04:02:50 +08:00
|
|
|
on:blur={handle_blur}
|
|
|
|
on:focus={handle_focus}
|
2023-09-13 09:36:29 +08:00
|
|
|
readonly={!filterable}
|
2023-02-24 05:32:18 +08:00
|
|
|
/>
|
2023-09-13 00:58:18 +08:00
|
|
|
{#if !disabled}
|
|
|
|
<DropdownArrow />
|
|
|
|
{/if}
|
2023-02-24 05:32:18 +08:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<DropdownOptions
|
2023-09-13 00:58:18 +08:00
|
|
|
{show_options}
|
|
|
|
{choices}
|
|
|
|
{filtered_indices}
|
2023-02-24 05:32:18 +08:00
|
|
|
{disabled}
|
2023-09-13 00:58:18 +08:00
|
|
|
selected_indices={selected_index === null ? [] : [selected_index]}
|
|
|
|
{active_index}
|
|
|
|
on:change={handle_option_selected}
|
2023-02-24 05:32:18 +08:00
|
|
|
/>
|
|
|
|
</div>
|
2022-04-26 22:48:39 +08:00
|
|
|
</label>
|
2023-01-18 04:47:40 +08:00
|
|
|
|
|
|
|
<style>
|
2023-08-04 06:01:18 +08:00
|
|
|
label:not(.container),
|
|
|
|
label:not(.container) .wrap,
|
|
|
|
label:not(.container) .wrap-inner,
|
|
|
|
label:not(.container) .secondary-wrap,
|
|
|
|
label:not(.container) input {
|
2023-07-18 01:05:46 +08:00
|
|
|
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
|
|
|
.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
|
|
|
|
2023-04-28 00:56:39 +08:00
|
|
|
.subdued {
|
|
|
|
color: var(--body-text-color-subdued);
|
|
|
|
}
|
2023-09-13 09:36:29 +08:00
|
|
|
|
|
|
|
input[readonly] {
|
|
|
|
cursor: pointer;
|
|
|
|
}
|
2023-01-18 04:47:40 +08:00
|
|
|
</style>
|