mirror of
https://github.com/gradio-app/gradio.git
synced 2025-04-12 12:40:29 +08:00
Move gr.Textbox
lines logic to frontend (#10785)
* changes * add changeset * test * fix max lines * fix e2e test * add changeset * changes * format * warnings textbox --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
This commit is contained in:
parent
6fd7fe8b2c
commit
fb8c1cb6d1
6
.changeset/giant-donkeys-pump.md
Normal file
6
.changeset/giant-donkeys-pump.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"@gradio/textbox": patch
|
||||
"gradio": patch
|
||||
---
|
||||
|
||||
fix:Move `gr.Textbox` lines logic to frontend
|
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
from collections.abc import Callable, Sequence
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
@ -38,8 +39,9 @@ class Textbox(FormComponent):
|
||||
self,
|
||||
value: str | Callable | None = None,
|
||||
*,
|
||||
type: Literal["text", "password", "email"] = "text",
|
||||
lines: int = 1,
|
||||
max_lines: int = 20,
|
||||
max_lines: int | None = None,
|
||||
placeholder: str | None = None,
|
||||
label: str | None = None,
|
||||
info: str | None = None,
|
||||
@ -57,7 +59,6 @@ class Textbox(FormComponent):
|
||||
elem_classes: list[str] | str | None = None,
|
||||
render: bool = True,
|
||||
key: int | str | None = None,
|
||||
type: Literal["text", "password", "email"] = "text",
|
||||
text_align: Literal["left", "right"] | None = None,
|
||||
rtl: bool = False,
|
||||
show_copy_button: bool = False,
|
||||
@ -68,8 +69,9 @@ class Textbox(FormComponent):
|
||||
"""
|
||||
Parameters:
|
||||
value: text to show in textbox. If a function is provided, the function will be called each time the app loads to set the initial value of this component.
|
||||
type: The type of textbox. One of: 'text' (which allows users to enter any text), 'password' (which masks text entered by the user), 'email' (which suggests email input to the browser). For "password" and "email" types, `lines` must be 1 and `max_lines` must be None or 1.
|
||||
lines: minimum number of line rows to provide in textarea.
|
||||
max_lines: maximum number of line rows to provide in textarea.
|
||||
max_lines: maximum number of line rows to provide in textarea. Must be at least `lines`. If not provided, the maximum number of lines is max(lines, 20) for "text" type, and 1 for "password" and "email" types.
|
||||
placeholder: placeholder hint to provide behind textarea.
|
||||
label: the label for this component, displayed above the component if `show_label` is `True` and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component corresponds to.
|
||||
info: additional component description, appears below the label in smaller font. Supports markdown / HTML syntax.
|
||||
@ -86,7 +88,6 @@ class Textbox(FormComponent):
|
||||
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.
|
||||
render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
|
||||
key: if assigned, will be used to assume identity across a re-render. Components that have the same key across a re-render will have their value preserved.
|
||||
type: The type of textbox. One of: 'text', 'password', 'email', Default is 'text'.
|
||||
text_align: How to align the text in the textbox, can be: "left", "right", or None (default). If None, the alignment is left if `rtl` is False, or right if `rtl` is True. Can only be changed if `type` is "text".
|
||||
rtl: If True and `type` is "text", sets the direction of the text to right-to-left (cursor appears on the left of the text). Default is False, which renders cursor on the right.
|
||||
show_copy_button: If True, includes a copy button to copy the text in the textbox. Only applies if show_label is True.
|
||||
@ -96,12 +97,20 @@ class Textbox(FormComponent):
|
||||
"""
|
||||
if type not in ["text", "password", "email"]:
|
||||
raise ValueError('`type` must be one of "text", "password", or "email".')
|
||||
if type in ["password", "email"]:
|
||||
if lines != 1:
|
||||
warnings.warn(
|
||||
"The `lines` parameter must be 1 for `type` of 'password' or 'email'. Setting `lines` to 1."
|
||||
)
|
||||
lines = 1
|
||||
if max_lines not in [None, 1]:
|
||||
warnings.warn(
|
||||
"The `max_lines` parameter must be None or 1 for `type` of 'password' or 'email'. Setting `max_lines` to 1."
|
||||
)
|
||||
max_lines = 1
|
||||
|
||||
self.lines = lines
|
||||
if type == "text":
|
||||
self.max_lines = max(lines, max_lines)
|
||||
else:
|
||||
self.max_lines = 1
|
||||
self.max_lines = max_lines
|
||||
self.placeholder = placeholder
|
||||
self.show_copy_button = show_copy_button
|
||||
self.submit_btn = submit_btn
|
||||
|
@ -12,23 +12,21 @@ test("clicking through tabs shows correct content", async ({ page }) => {
|
||||
await expect(page.locator("body")).not.toContainText("Incomplete Tasks (0)");
|
||||
await expect(page.locator("body")).toContainText("Incomplete Tasks (1)");
|
||||
await expect(page.locator("body")).toContainText("Complete Tasks (0)");
|
||||
await expect(page.locator("textarea").nth(1)).toHaveValue("eat");
|
||||
await expect(page.locator("input").nth(0)).toHaveValue("eat");
|
||||
|
||||
await input_text.fill("pray");
|
||||
await input_text.press("Enter");
|
||||
|
||||
await expect(page.locator("body")).toContainText("Incomplete Tasks (2)");
|
||||
await expect(page.locator("body")).toContainText("Complete Tasks (0)");
|
||||
await expect(page.locator("textarea").nth(2)).toHaveValue("pray");
|
||||
await expect(page.locator("input").nth(1)).toHaveValue("pray");
|
||||
|
||||
await input_text.fill("love");
|
||||
await input_text.press("Enter");
|
||||
|
||||
await expect(page.locator("body")).toContainText("Incomplete Tasks (3)");
|
||||
await expect(page.locator("body")).toContainText("Complete Tasks (0)");
|
||||
await expect(page.locator("textarea").nth(1)).toHaveValue("eat");
|
||||
await expect(page.locator("textarea").nth(2)).toHaveValue("pray");
|
||||
await expect(page.locator("textarea").nth(3)).toHaveValue("love");
|
||||
await expect(page.locator("input").nth(2)).toHaveValue("love");
|
||||
|
||||
const done_btn_for_eat = page
|
||||
.locator("button")
|
||||
|
@ -32,7 +32,7 @@
|
||||
export let lines: number;
|
||||
export let placeholder = "";
|
||||
export let show_label: boolean;
|
||||
export let max_lines: number;
|
||||
export let max_lines: number | undefined = undefined;
|
||||
export let type: "text" | "password" | "email" = "text";
|
||||
export let container = true;
|
||||
export let scale: number | null = null;
|
||||
@ -80,7 +80,7 @@
|
||||
{type}
|
||||
{rtl}
|
||||
{text_align}
|
||||
max_lines={!max_lines ? lines + 1 : max_lines}
|
||||
{max_lines}
|
||||
{placeholder}
|
||||
{submit_btn}
|
||||
{stop_btn}
|
||||
|
@ -19,7 +19,7 @@
|
||||
export let disabled = false;
|
||||
export let show_label = true;
|
||||
export let container = true;
|
||||
export let max_lines: number;
|
||||
export let max_lines: number | undefined = undefined;
|
||||
export let type: "text" | "password" | "email" = "text";
|
||||
export let show_copy_button = false;
|
||||
export let submit_btn: string | boolean | null = null;
|
||||
@ -37,10 +37,21 @@
|
||||
let can_scroll: boolean;
|
||||
let previous_scroll_top = 0;
|
||||
let user_has_scrolled_up = false;
|
||||
let _max_lines: number;
|
||||
|
||||
const show_textbox_border = !submit_btn;
|
||||
|
||||
$: value, el && lines !== max_lines && resize({ target: el });
|
||||
$: if (max_lines === undefined) {
|
||||
if (type === "text") {
|
||||
_max_lines = Math.max(lines, 20);
|
||||
} else {
|
||||
_max_lines = 1;
|
||||
}
|
||||
} else {
|
||||
_max_lines = Math.max(max_lines, lines);
|
||||
}
|
||||
|
||||
$: value, el && lines !== _max_lines && resize({ target: el });
|
||||
|
||||
$: if (value === null) value = "";
|
||||
|
||||
@ -119,7 +130,7 @@
|
||||
e.key === "Enter" &&
|
||||
!e.shiftKey &&
|
||||
lines === 1 &&
|
||||
max_lines >= 1
|
||||
_max_lines >= 1
|
||||
) {
|
||||
e.preventDefault();
|
||||
dispatch("submit");
|
||||
@ -153,7 +164,7 @@
|
||||
event: Event | { target: HTMLTextAreaElement | HTMLInputElement }
|
||||
): Promise<void> {
|
||||
await tick();
|
||||
if (lines === max_lines) return;
|
||||
if (lines === _max_lines) return;
|
||||
|
||||
const target = event.target as HTMLTextAreaElement;
|
||||
const computed_styles = window.getComputedStyle(target);
|
||||
@ -162,9 +173,9 @@
|
||||
const line_height = parseFloat(computed_styles.lineHeight);
|
||||
|
||||
let max =
|
||||
max_lines === undefined
|
||||
_max_lines === undefined
|
||||
? false
|
||||
: padding_top + padding_bottom + line_height * max_lines;
|
||||
: padding_top + padding_bottom + line_height * _max_lines;
|
||||
let min = padding_top + padding_bottom + lines * line_height;
|
||||
|
||||
target.style.height = "1px";
|
||||
@ -185,7 +196,7 @@
|
||||
_el: HTMLTextAreaElement,
|
||||
_value: string
|
||||
): any | undefined {
|
||||
if (lines === max_lines) return;
|
||||
if (lines === _max_lines) return;
|
||||
_el.style.overflowY = "scroll";
|
||||
_el.addEventListener("input", resize);
|
||||
|
||||
@ -220,7 +231,7 @@
|
||||
<BlockTitle {root} {show_label} {info}>{label}</BlockTitle>
|
||||
|
||||
<div class="input-container">
|
||||
{#if lines === 1 && max_lines === 1}
|
||||
{#if lines === 1 && _max_lines === 1}
|
||||
{#if type === "text"}
|
||||
<input
|
||||
data-testid="textbox"
|
||||
|
@ -17,7 +17,7 @@ class TestTextbox:
|
||||
assert text_input.postprocess(2.14) == "2.14" # type: ignore
|
||||
assert text_input.get_config() == {
|
||||
"lines": 1,
|
||||
"max_lines": 20,
|
||||
"max_lines": None,
|
||||
"placeholder": None,
|
||||
"value": None,
|
||||
"name": "textbox",
|
||||
@ -86,9 +86,3 @@ class TestTextbox:
|
||||
ValueError, match='`type` must be one of "text", "password", or "email".'
|
||||
):
|
||||
gr.Textbox(type="boo") # type: ignore
|
||||
|
||||
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
|
||||
|
@ -556,7 +556,6 @@ class TestProcessExamples:
|
||||
assert response.json()["data"] == [
|
||||
{
|
||||
"lines": 1,
|
||||
"max_lines": 20,
|
||||
"show_label": True,
|
||||
"container": True,
|
||||
"min_width": 160,
|
||||
@ -578,7 +577,6 @@ class TestProcessExamples:
|
||||
assert response.json()["data"] == [
|
||||
{
|
||||
"lines": 1,
|
||||
"max_lines": 20,
|
||||
"show_label": True,
|
||||
"container": True,
|
||||
"min_width": 160,
|
||||
|
Loading…
x
Reference in New Issue
Block a user