Skip to content

Commit

Permalink
gr.Dropdown() now supports values with arbitrary characters and doe…
Browse files Browse the repository at this point in the history
…sn't clear value when re-focused (#5039)

* changelog

* changes

* add changeset

* fixes

* revert

* add changeset

* python reorder

* add changeset

* fix test

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
  • Loading branch information
abidlabs and gradio-pr-bot committed Jul 31, 2023
1 parent d970067 commit 620e464
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 40 deletions.
6 changes: 6 additions & 0 deletions .changeset/smooth-humans-flash.md
@@ -0,0 +1,6 @@
---
"@gradio/form": patch
"gradio": patch
---

fix:`gr.Dropdown()` now supports values with arbitrary characters and doesn't clear value when re-focused
4 changes: 2 additions & 2 deletions gradio/components/dropdown.py
Expand Up @@ -46,6 +46,7 @@ def __init__(
value: str | list[str] | Callable | None = None,
type: Literal["value", "index"] = "value",
multiselect: bool | None = None,
allow_custom_value: bool = False,
max_choices: int | None = None,
label: str | None = None,
info: str | None = None,
Expand All @@ -58,7 +59,6 @@ def __init__(
visible: bool = True,
elem_id: str | None = None,
elem_classes: list[str] | str | None = None,
allow_custom_value: bool = False,
**kwargs,
):
"""
Expand All @@ -67,6 +67,7 @@ def __init__(
value: default value(s) selected in dropdown. If None, no value is selected by default. If callable, the function will be called whenever the app loads to set the initial value of the component.
type: Type of value to be returned by component. "value" returns the string of the choice selected, "index" returns the index of the choice selected.
multiselect: if True, multiple choices can be selected.
allow_custom_value: If True, allows user to enter a custom value that is not in the list of choices. Only applies if `multiselect` is False.
max_choices: maximum number of choices that can be selected. If None, no limit is enforced.
label: component name in interface.
info: additional component description.
Expand All @@ -79,7 +80,6 @@ def __init__(
visible: If False, component will be hidden.
elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
allow_custom_value: If True, allows user to enter a custom value that is not in the list of choices.
"""
self.choices = [str(choice) for choice in choices] if choices else []
valid_types = ["value", "index"]
Expand Down
49 changes: 25 additions & 24 deletions js/form/src/Dropdown.svelte
Expand Up @@ -6,19 +6,19 @@
import type { SelectData } from "@gradio/utils";
export let label: string;
export let info: string | undefined = undefined;
export let value: string | Array<string> | undefined;
export let value: string | string[] | undefined;
let old_value = Array.isArray(value) ? value.slice() : value;
export let value_is_output: boolean = false;
export let multiselect: boolean = false;
export let value_is_output = false;
export let multiselect = false;
export let max_choices: number;
export let choices: Array<string>;
export let disabled: boolean = false;
export let choices: string[];
export let disabled = false;
export let show_label: boolean;
export let container: boolean = true;
export let allow_custom_value: boolean = false;
export let container = true;
export let allow_custom_value = false;
const dispatch = createEventDispatcher<{
change: string | Array<string> | undefined;
change: string | string[] | undefined;
input: undefined;
select: SelectData;
blur: undefined;
Expand All @@ -41,7 +41,7 @@
activeOption = filtered.length ? filtered[0] : null;
}
function handle_change() {
function handle_change(): void {
dispatch("change", value);
if (!value_is_output) {
dispatch("input");
Expand All @@ -57,8 +57,8 @@
}
}
function add(option: string) {
value = value as Array<string>;
function add(option: string): void {
value = value as string[];
if (!max_choices || value.length < max_choices) {
value.push(option);
dispatch("select", {
Expand All @@ -70,8 +70,8 @@
value = value;
}
function remove(option: string) {
value = value as Array<string>;
function remove(option: string): void {
value = value as string[];
value = value.filter((v: string) => v !== option);
dispatch("select", {
index: choices.indexOf(option),
Expand All @@ -80,13 +80,13 @@
});
}
function remove_all(e: any) {
function remove_all(e: any): void {
value = [];
inputValue = "";
e.preventDefault();
}
function handleOptionMousedown(e: any) {
function handleOptionMousedown(e: any): void {
const option = e.detail.target.dataset.value;
if (allow_custom_value) {
inputValue = option;
Expand All @@ -109,11 +109,19 @@
value: option,
selected: true
});
return;
}
}
}
function handleFocus(): void {
showOptions = !showOptions;
if (showOptions) {
filtered = choices;
} else {
filterInput.blur();
}
}
function handleKeydown(e: any) {
if (e.key === "Enter" && activeOption != undefined) {
if (!multiselect) {
Expand Down Expand Up @@ -200,14 +208,7 @@
autocomplete="off"
bind:value={inputValue}
bind:this={filterInput}
on:focus={() => {
showOptions = !showOptions;
if (showOptions) {
inputValue = "";
} else {
filterInput.blur();
}
}}
on:focus={handleFocus}
on:keydown={handleKeydown}
on:keyup={() => {
if (allow_custom_value) {
Expand Down
23 changes: 12 additions & 11 deletions js/form/src/DropdownOptions.svelte
@@ -1,11 +1,11 @@
<script lang="ts">
import { fly } from "svelte/transition";
import { createEventDispatcher } from "svelte";
export let value: string | Array<string> | undefined = undefined;
export let filtered: Array<string>;
export let showOptions: boolean = false;
export let value: string | string[] | undefined = undefined;
export let filtered: string[];
export let showOptions = false;
export let activeOption: string | null;
export let disabled: boolean = false;
export let disabled = false;
let distance_from_top: number;
let distance_from_bottom: number;
Expand All @@ -16,15 +16,15 @@
let top: string | null, bottom: string | null, max_height: number;
let innerHeight: number;
const calculate_window_distance = () => {
const calculate_window_distance = (): void => {
const { top: ref_top, bottom: ref_bottom } =
refElement.getBoundingClientRect();
distance_from_top = ref_top;
distance_from_bottom = innerHeight - ref_bottom;
};
let scroll_timeout: NodeJS.Timeout | null = null;
const scroll_listener = () => {
const scroll_listener = (): void => {
if (!showOptions) return;
if (scroll_timeout !== null) {
clearTimeout(scroll_timeout);
Expand All @@ -39,11 +39,12 @@
$: {
if (showOptions && refElement) {
if (listElement && typeof value === "string") {
let el = document.querySelector(
`li[data-value="${value}"]`
) as HTMLLIElement;
if (el) {
listElement.scrollTo(0, el.offsetTop);
let elements = listElement.querySelectorAll('li');
for (const element of elements) {
if (element.getAttribute('data-value') === value) {
listElement.scrollTo(0, (element as HTMLLIElement).offsetTop);
break;
}
}
}
calculate_window_distance();
Expand Down
4 changes: 2 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tsconfig.json
Expand Up @@ -2,7 +2,7 @@
"compilerOptions": {
"moduleResolution": "node",
"module": "es2020",
"lib": ["es2020", "DOM"],
"lib": ["es2020", "DOM", "dom.iterable"],
"target": "es2020",
/**
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
Expand Down

1 comment on commit 620e464

@vercel
Copy link

@vercel vercel bot commented on 620e464 Jul 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.