Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/calm-columns-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@gradio/column": patch
"@gradio/core": patch
"@gradio/dataset": patch
"@gradio/html": patch
"@gradio/json": patch
"@gradio/plot": patch
"@gradio/tabitem": patch
"@gradio/textbox": patch
---

fix: restore shared props lost during frontend prop partitioning
70 changes: 70 additions & 0 deletions demo/shared_props/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import matplotlib.pyplot as plt

import gradio as gr
from gradio.media import get_image


def validate_text(value: str):
return gr.validate(bool(value.strip()), "Text is required")


def echo(value: str):
return value


figure, axis = plt.subplots()
axis.plot([0, 1, 2], [0, 1, 0])


with gr.Blocks() as demo:
with gr.Row():
with gr.Column(variant="panel", elem_id="panel-column"):
gr.Markdown("Panel column")
with gr.Column(variant="compact", elem_id="compact-column"):
gr.Button("Compact one")
gr.Button("Compact two")

with gr.Row():
gr.HTML(
"Padded HTML",
container=True,
padding=True,
elem_id="padded-html",
)
gr.HTML(
"Unpadded HTML",
container=True,
padding=False,
elem_id="unpadded-html",
)

gr.JSON({"shared": "theme"}, elem_id="shared-json")
gr.Plot(figure, elem_id="shared-plot")

with gr.Tabs():
with gr.Tab("Scaled tab", scale=2, elem_id="scaled-tab"):
gr.Markdown("Scaled tab content")
with gr.Tab("Other tab"):
gr.Markdown("Other tab content")

image_component = gr.Image(visible=False)
gr.Dataset(
components=[image_component],
samples=[[get_image("cheetah1.jpg")]],
label="Image examples",
elem_id="shared-dataset",
)

validated_text = gr.Textbox(label="Validated text")
validate_button = gr.Button("Validate text")
validated_output = gr.Textbox(label="Validated output", interactive=False)
validate_button.click(
echo,
validated_text,
validated_output,
validator=validate_text,
)


if __name__ == "__main__":
demo.launch()
2 changes: 1 addition & 1 deletion js/column/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
);
</script>

<BaseColumn {...gradio.shared}>
<BaseColumn {...gradio.shared} {...gradio.props}>
<slot />
</BaseColumn>

Expand Down
2 changes: 2 additions & 0 deletions js/core/src/Blocks.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@
{
root,
theme: theme_mode,
theme_mode,
version,
api_prefix,
max_file_size,
Expand Down Expand Up @@ -244,6 +245,7 @@
app_tree.reload(components, layout, dependencies, {
root,
theme: theme_mode,
theme_mode,
version,
api_prefix,
max_file_size,
Expand Down
1 change: 1 addition & 0 deletions js/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export type LoadingComponent = Promise<{
export interface AppConfig {
root: string;
theme: string;
theme_mode: ThemeMode;
version: string;
max_file_size?: number;
autoscroll: boolean;
Expand Down
35 changes: 35 additions & 0 deletions js/dataset/Dataset.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { afterEach, describe, expect, test } from "vitest";
import { cleanup, render, waitFor } from "@self/tootils/render";

import Dataset from "./Index.svelte";
import DatasetRootExample from "./DatasetRootExample.svelte";

describe("Dataset", () => {
afterEach(() => cleanup());

test("forwards the shared root to example components", async () => {
const result = await render(Dataset, {
components: [{ name: "textbox", class_id: "textbox" }],
component_props: [{}],
headers: ["Example"],
samples: [["sample"]],
sample_labels: null,
value: null,
root: "/gradio-root",
proxy_url: null,
samples_per_page: 10,
layout: "gallery",
show_label: false,
load_component: () => ({
component: Promise.resolve({ default: DatasetRootExample }),
runtime: false
})
});

await waitFor(() => {
expect(result.getByTestId("dataset-root")).toHaveTextContent(
"/gradio-root"
);
});
});
});
5 changes: 5 additions & 0 deletions js/dataset/DatasetRootExample.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script lang="ts">
let { root }: { root?: string } = $props();
</script>

<span data-testid="dataset-root">{root ?? "missing"}</span>
1 change: 1 addition & 0 deletions js/dataset/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
onselect={(data) => gradio.dispatch("select", data)}
load_component={gradio.shared.load_component}
{...gradio.props}
root={gradio.shared.root}
{samples}
/>
</Block>
Expand Down
1 change: 0 additions & 1 deletion js/dataset/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export interface DatasetProps {
samples: any[][] | null;
sample_labels: string[] | null;
value: number | null;
root: string;
proxy_url: null | string;
samples_per_page: number;

Expand Down
2 changes: 1 addition & 1 deletion js/html/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
elem_id={gradio.shared.elem_id}
elem_classes={gradio.shared.elem_classes}
container={gradio.shared.container}
padding={gradio.props.padding !== false}
padding={gradio.shared.padding !== false}
overflow_behavior="visible"
>
{#if gradio.shared.show_label && gradio.props.buttons && gradio.props.buttons.length > 0}
Expand Down
1 change: 0 additions & 1 deletion js/html/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export interface HTMLProps {
head: string | null;
component_class_name: string;
buttons: (string | CustomButton)[] | null;
padding: boolean;
}

export interface HTMLEvents {
Expand Down
2 changes: 1 addition & 1 deletion js/json/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
<JSON
value={gradio.props.value}
open={gradio.props.open}
theme_mode={gradio.props.theme_mode}
theme_mode={gradio.shared.theme_mode}
show_indices={gradio.props.show_indices}
show_copy_button={gradio.props.buttons == null
? true
Expand Down
1 change: 0 additions & 1 deletion js/json/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export interface JSONProps {
height: number | string | undefined;
min_height: number | string | undefined;
max_height: number | string | undefined;
theme_mode: "system" | "light" | "dark";
buttons: (string | CustomButton)[];
}

Expand Down
2 changes: 1 addition & 1 deletion js/plot/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
/>
<Plot
value={gradio.props.value}
theme_mode={gradio.props.theme_mode}
theme_mode={gradio.shared.theme_mode}
show_label={gradio.shared.show_label}
caption={gradio.props.caption}
bokeh_version={gradio.props.bokeh_version}
Expand Down
1 change: 0 additions & 1 deletion js/plot/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export type ThemeMode = "system" | "light" | "dark";

export interface PlotProps {
value: null | string;
theme_mode: ThemeMode;
caption: string;
bokeh_version: string | null;
show_actions_button: boolean;
Expand Down
46 changes: 46 additions & 0 deletions js/spa/test/shared_props.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { expect, test } from "@self/tootils";

test("shared props reach affected components in a running app", async ({
page
}) => {
await page.emulateMedia({ colorScheme: "dark" });
await page.reload();

await expect(page.locator("#panel-column")).toHaveClass(/\bpanel\b/);
await expect(page.locator("#compact-column")).toHaveClass(/\bcompact\b/);

await expect(page.locator("#padded-html")).toHaveClass(/\bpadded\b/);
await expect(page.locator("#unpadded-html")).not.toHaveClass(/\bpadded\b/);

await expect(page.locator("#shared-json .json-node.root")).toHaveClass(
/\bdark-mode\b/
);

const plot = page.locator("#shared-plot [data-testid='matplotlib'] img");
await expect(plot).toBeVisible();
await expect
.poll(() => plot.evaluate((image: HTMLImageElement) => image.naturalWidth))
.toBeGreaterThan(0);

const scaled_tab = page.getByRole("tabpanel").filter({
has: page.getByText("Scaled tab content")
});
await expect(scaled_tab).toHaveCSS("flex-grow", "2");

const dataset_image = page.locator("#shared-dataset img");
await expect(dataset_image).toBeVisible();
await expect
.poll(() =>
dataset_image.evaluate((image: HTMLImageElement) => image.naturalWidth)
)
.toBeGreaterThan(0);

await page.getByRole("button", { name: "Validate text" }).click();
await expect(page.getByText("Text is required")).toBeVisible();

await page.getByLabel("Validated text").fill("valid value");
await expect(page.getByText("Text is required")).toBeHidden();

await page.getByRole("button", { name: "Validate text" }).click();
await expect(page.getByLabel("Validated output")).toHaveValue("valid value");
});
2 changes: 1 addition & 1 deletion js/tabitem/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
interactive={gradio.shared.interactive}
id={gradio.props.id}
order={gradio.props.order}
scale={gradio.props.scale}
scale={gradio.shared.scale}
component_id={gradio.props.component_id}
onselect={(data) => gradio.dispatch("select", data)}
>
Expand Down
1 change: 0 additions & 1 deletion js/tabitem/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export interface TabItemProps {
visible: boolean | "hidden";
interactive: boolean;
order: number;
scale: number;
component_id: number;
}

Expand Down
13 changes: 10 additions & 3 deletions js/textbox/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

async function handle_input(value: string): Promise<void> {
if (!gradio.shared || !gradio.props) return;
gradio.props.validation_error = null;
clear_validation_error();
gradio.props.value = value;
await tick();
gradio.dispatch("input");
Expand All @@ -44,9 +44,16 @@

function handle_change(value: string): void {
if (!gradio.shared || !gradio.props) return;
gradio.props.validation_error = null;
clear_validation_error();
gradio.props.value = value;
}

function clear_validation_error(): void {
gradio.shared.validation_error = null;
if (gradio.shared.loading_status) {
gradio.shared.loading_status.validation_error = null;
}
}
</script>

<Block
Expand Down Expand Up @@ -94,7 +101,7 @@
onchange={handle_change}
oninput={handle_input}
onsubmit={() => {
gradio.shared.validation_error = null;
clear_validation_error();
gradio.dispatch("submit");
}}
onblur={() => gradio.dispatch("blur")}
Expand Down
20 changes: 20 additions & 0 deletions js/textbox/Textbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,26 @@ describe("Textbox", () => {

expect(item.value).toBe("hi some text");
});

test.each(["input", "change"] as const)(
"%s clears the shared validation error",
async (event_name) => {
const result = await render(Textbox, {
...default_props,
value: "",
validation_error: "Shared error",
loading_status: { validation_error: "Required" }
});
const textbox = result.getByRole("textbox");

expect(result.getByText("Required")).toBeVisible();
await fireEvent[event_name](textbox, { target: { value: "valid" } });

await waitFor(() => {
expect(result.queryByText("Required")).not.toBeInTheDocument();
});
}
);
});

describe("Props: type", () => {
Expand Down
1 change: 0 additions & 1 deletion js/textbox/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export interface TextboxProps {
autoscroll: boolean;
max_length: number;
html_attributes: InputHTMLAttributes;
validation_error: string | null;
}

type FullAutoFill =
Expand Down
2 changes: 1 addition & 1 deletion js/utils/src/utils.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface SharedProps {
client: Client;
scale: number;
min_width: number;
padding: number;
padding: boolean;
load_component: load_component;
loading_status?: any;
label: string;
Expand Down
Loading