diff --git a/CHANGELOG.md b/CHANGELOG.md index e7c972d2cb..58f410d932 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Upcoming Release ## New Features: -No changes to highlight. +* Add support for `'password'` and `'email'` types to `Textbox`. [@pngwn](https://github.com/pngwn) in [PR 2653](https://github.com/gradio-app/gradio/pull/2653) ## Bug Fixes: * Updated the minimum FastApi used in tests to version 0.87 [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2647](https://github.com/gradio-app/gradio/pull/2647) diff --git a/demo/blocks_inputs/run.py b/demo/blocks_inputs/run.py index 502b1ed914..b259312d8e 100644 --- a/demo/blocks_inputs/run.py +++ b/demo/blocks_inputs/run.py @@ -1,36 +1,46 @@ import gradio as gr import os + def combine(a, b): return a + " " + b + def mirror(x): return x + with gr.Blocks() as demo: - + txt = gr.Textbox(label="Input", lines=2) txt_2 = gr.Textbox(label="Input 2") txt_3 = gr.Textbox(value="", label="Output") btn = gr.Button(value="Submit") btn.click(combine, inputs=[txt, txt_2], outputs=[txt_3]) - + with gr.Row(): im = gr.Image() im_2 = gr.Image() - + btn = gr.Button(value="Mirror Image") btn.click(mirror, inputs=[im], outputs=[im_2]) - + gr.Markdown("## Text Examples") - gr.Examples([["hi", "Adam"], ["hello", "Eve"]], [txt, txt_2], txt_3, combine, cache_examples=True) + gr.Examples( + [["hi", "Adam"], ["hello", "Eve"]], + [txt, txt_2], + txt_3, + combine, + cache_examples=True, + ) gr.Markdown("## Image Examples") gr.Examples( examples=[os.path.join(os.path.dirname(__file__), "lion.jpg")], inputs=im, outputs=im_2, fn=mirror, - cache_examples=True) + cache_examples=True, + ) if __name__ == "__main__": demo.launch() diff --git a/gradio/components.py b/gradio/components.py index 9cf67d0d3d..82f0363d0d 100644 --- a/gradio/components.py +++ b/gradio/components.py @@ -281,6 +281,7 @@ class Textbox( interactive: Optional[bool] = None, visible: bool = True, elem_id: Optional[str] = None, + type: str = "text", **kwargs, ): """ @@ -294,9 +295,14 @@ class Textbox( interactive: if True, will be rendered as an editable textbox; if False, editing will be disabled. If not provided, this is inferred based on whether the component is used as an input or output. 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. + type: The type of textbox. One of: 'text', 'password', 'email', Default is 'text'. """ + if type not in ["text", "password", "email"]: + raise ValueError('`type` must be one of "text", "password", or "email".') + + # self.lines = lines - self.max_lines = max_lines + self.max_lines = max_lines if type == "text" else 1 self.placeholder = placeholder self.interpret_by_tokens = True IOComponent.__init__( @@ -311,6 +317,7 @@ class Textbox( ) self.cleared_value = "" self.test_input = value + self.type = type def get_config(self): return { @@ -318,6 +325,7 @@ class Textbox( "max_lines": self.max_lines, "placeholder": self.placeholder, "value": self.value, + "type": self.type, **IOComponent.get_config(self), } @@ -331,6 +339,7 @@ class Textbox( show_label: Optional[bool] = None, visible: Optional[bool] = None, interactive: Optional[bool] = None, + type: Optional[str] = None, ): updated_config = { "lines": lines, @@ -340,6 +349,7 @@ class Textbox( "show_label": show_label, "visible": visible, "value": value, + "type": type, "__type__": "update", } return IOComponent.add_interactive_to_config(updated_config, interactive) diff --git a/gradio/inputs.py b/gradio/inputs.py index 6b1c7f9f8c..184c965593 100644 --- a/gradio/inputs.py +++ b/gradio/inputs.py @@ -19,7 +19,7 @@ class Textbox(components.Textbox): placeholder: Optional[str] = None, default: str = "", numeric: Optional[bool] = False, - type: Optional[str] = "str", + type: Optional[str] = "text", label: Optional[str] = None, optional: bool = False, ): diff --git a/gradio/test_data/blocks_configs.py b/gradio/test_data/blocks_configs.py index 6a1660e613..11dfa3a8da 100644 --- a/gradio/test_data/blocks_configs.py +++ b/gradio/test_data/blocks_configs.py @@ -126,6 +126,7 @@ XRAY_CONFIG = { "lines": 1, "max_lines": 20, "value": "", + "type": "text", "show_label": True, "name": "textbox", "visible": True, @@ -358,6 +359,7 @@ XRAY_CONFIG_DIFF_IDS = { "lines": 1, "max_lines": 20, "value": "", + "type": "text", "show_label": True, "name": "textbox", "visible": True, @@ -593,6 +595,7 @@ XRAY_CONFIG_WITH_MISTAKE = { "lines": 1, "value": "", "name": "textbox", + "type": "text", "style": {}, }, }, diff --git a/test/test_blocks.py b/test/test_blocks.py index 03ae152b45..80ff3b004f 100644 --- a/test/test_blocks.py +++ b/test/test_blocks.py @@ -714,6 +714,8 @@ class TestSpecificUpdate: "placeholder": None, "label": None, "show_label": None, + "type": None, + "type": None, "visible": None, "value": gr.components._Keywords.NO_VALUE, "__type__": "update", @@ -729,6 +731,7 @@ class TestSpecificUpdate: "placeholder": None, "label": None, "show_label": None, + "type": None, "visible": None, "value": gr.components._Keywords.NO_VALUE, "__type__": "update", diff --git a/test/test_components.py b/test/test_components.py index 3595e8aecf..d2f0f0bba5 100644 --- a/test/test_components.py +++ b/test/test_components.py @@ -1,5 +1,5 @@ """ -Tests for all of the componets defined in components.py. Tests are divided into two types: +Tests for all of the components defined in components.py. Tests are divided into two types: 1. test_component_functions() are unit tests that check essential functions of a component, the functions that are checked are documented in the docstring. 2. test_in_interface() are functional tests that check a component's functionalities inside an Interface. Please do not use Interface.launch() in this file, as it slow downs the tests. """ @@ -59,9 +59,6 @@ class TestTextbox: assert text_input.postprocess(2.14) == "2.14" assert text_input.serialize("Hello World!", True) == "Hello World!" - with pytest.warns(Warning): - _ = gr.Textbox(type="number") - assert text_input.tokenize("Hello World! Gradio speaking.") == ( ["Hello", "World!", "Gradio", "speaking."], [ @@ -90,6 +87,7 @@ class TestTextbox: "value": "", "name": "textbox", "show_label": True, + "type": "text", "label": None, "style": {}, "elem_id": None, @@ -166,6 +164,18 @@ class TestTextbox: assert component.get_config().get("value") == "abc" assert component.get_config().get("lines") == 4 + def test_faulty_type(self): + with pytest.raises( + ValueError, match='`type` must be one of "text", "password", or "email".' + ): + gr.Textbox(type="boo") + + def test_max_lines(self): + assert gr.Textbox(type="password").get_config().get("max_lines") == 1 + assert gr.Textbox(type="email").get_config().get("max_lines") == 1 + assert gr.Textbox(type="text").get_config().get("max_lines") == 20 + assert gr.Textbox().get_config().get("max_lines") == 20 + class TestNumber: def test_component_functions(self): diff --git a/ui/packages/app/src/components/Textbox/Textbox.svelte b/ui/packages/app/src/components/Textbox/Textbox.svelte index fac04a7d7a..33b535a610 100644 --- a/ui/packages/app/src/components/Textbox/Textbox.svelte +++ b/ui/packages/app/src/components/Textbox/Textbox.svelte @@ -15,6 +15,7 @@ export let placeholder: string = ""; export let show_label: boolean; export let max_lines: number | false; + export let type: "text" | "password" | "email" = "text"; export let style: Styles = {}; @@ -35,6 +36,7 @@ {label} {show_label} {lines} + {type} max_lines={!max_lines && mode === "static" ? lines + 1 : max_lines} {placeholder} on:change diff --git a/ui/packages/form/src/Textbox.svelte b/ui/packages/form/src/Textbox.svelte index 465e966bd0..43a56c1881 100644 --- a/ui/packages/form/src/Textbox.svelte +++ b/ui/packages/form/src/Textbox.svelte @@ -9,6 +9,7 @@ export let disabled = false; export let show_label: boolean = true; export let max_lines: number | false; + export let type: "text" | "password" | "email" = "text"; let el: HTMLTextAreaElement | HTMLInputElement; @@ -93,17 +94,45 @@ {label} {#if lines === 1 && max_lines === 1} - + {#if type === "text"} + + {:else if type === "password"} + + {:else if type === "email"} + + {/if} {:else}