diff --git a/.changeset/forty-hornets-share.md b/.changeset/forty-hornets-share.md
new file mode 100644
index 0000000000..66f279c6a2
--- /dev/null
+++ b/.changeset/forty-hornets-share.md
@@ -0,0 +1,6 @@
+---
+"@gradio/gallery": minor
+"gradio": minor
+---
+
+feat:Adds `change()` event to `gr.Gallery`
diff --git a/gradio/components/gallery.py b/gradio/components/gallery.py
index 7f19ee66eb..429202257e 100644
--- a/gradio/components/gallery.py
+++ b/gradio/components/gallery.py
@@ -15,6 +15,7 @@ from gradio import utils
from gradio.components.base import IOComponent, _Keywords
from gradio.deprecation import warn_deprecation, warn_style_method_deprecation
from gradio.events import (
+ Changeable,
EventListenerMethod,
Selectable,
)
@@ -23,7 +24,7 @@ set_documentation_group("component")
@document()
-class Gallery(IOComponent, GallerySerializable, Selectable):
+class Gallery(IOComponent, GallerySerializable, Changeable, Selectable):
"""
Used to display a list of images as a gallery that can be scrolled through.
Preprocessing: this component does *not* accept input.
diff --git a/js/gallery/Gallery.test.ts b/js/gallery/Gallery.test.ts
new file mode 100644
index 0000000000..91659d14b0
--- /dev/null
+++ b/js/gallery/Gallery.test.ts
@@ -0,0 +1,72 @@
+import { test, describe, assert, afterEach, vi } from "vitest";
+import { cleanup, render } from "@gradio/tootils";
+import { setupi18n } from "../app/src/i18n";
+
+import Gallery from "./static";
+import type { LoadingStatus } from "@gradio/statustracker";
+
+const loading_status: LoadingStatus = {
+ eta: 0,
+ queue_position: 1,
+ queue_size: 1,
+ status: "complete" as LoadingStatus["status"],
+ scroll_to_output: false,
+ visible: true,
+ fn_index: 0,
+ show_progress: "full"
+};
+
+describe("Gallery", () => {
+ afterEach(() => {
+ cleanup();
+ vi.useRealTimers();
+ });
+ beforeEach(() => {
+ setupi18n();
+ });
+ test("renders the image provided", async () => {
+ const { getByTestId } = await render(Gallery, {
+ show_label: true,
+ label: "Gallery",
+ loading_status: loading_status,
+ preview: true,
+ root: "",
+ root_url: "",
+ value: [
+ "https://gradio-static-files.s3.us-west-2.amazonaws.com/header-image.jpg"
+ ]
+ });
+ let item = getByTestId("detailed-image") as HTMLImageElement;
+ assert.equal(
+ item.src,
+ "https://gradio-static-files.s3.us-west-2.amazonaws.com/header-image.jpg"
+ );
+ });
+
+ test("triggers the change event if and only if the images change", async () => {
+ const { listen, component } = await render(Gallery, {
+ show_label: true,
+ label: "Gallery",
+ loading_status: loading_status,
+ preview: true,
+ root: "",
+ root_url: "",
+ value: [
+ "https://gradio-static-files.s3.us-west-2.amazonaws.com/header-image.jpg"
+ ]
+ });
+ const change_event = listen("change");
+
+ await component.$set({
+ value: [
+ "https://gradio-static-files.s3.us-west-2.amazonaws.com/header-image.jpg"
+ ]
+ });
+ assert.equal(change_event.callCount, 0);
+
+ await component.$set({
+ value: ["https://gradio-static-files.s3.us-west-2.amazonaws.com/lion.jpg"]
+ });
+ assert.equal(change_event.callCount, 1);
+ });
+});
diff --git a/js/gallery/package.json b/js/gallery/package.json
index 3e68d56589..3a805ec2d2 100644
--- a/js/gallery/package.json
+++ b/js/gallery/package.json
@@ -13,7 +13,8 @@
"@gradio/image": "workspace:^",
"@gradio/statustracker": "workspace:^",
"@gradio/upload": "workspace:^",
- "@gradio/utils": "workspace:^"
+ "@gradio/utils": "workspace:^",
+ "dequal": "^2.0.2"
},
"main_changeset": true,
"exports": {
diff --git a/js/gallery/static/Gallery.svelte b/js/gallery/static/Gallery.svelte
index 12f3526411..68097ea208 100644
--- a/js/gallery/static/Gallery.svelte
+++ b/js/gallery/static/Gallery.svelte
@@ -2,7 +2,7 @@
import { BlockLabel, Empty, ShareButton } from "@gradio/atoms";
import { ModifyUpload } from "@gradio/upload";
import type { SelectData } from "@gradio/utils";
-
+ import { dequal } from "dequal";
import { createEventDispatcher } from "svelte";
import { tick } from "svelte";
import { _ } from "svelte-i18n";
@@ -30,6 +30,7 @@
export let show_download_button = false;
const dispatch = createEventDispatcher<{
+ change: undefined;
select: SelectData;
}>();
@@ -53,7 +54,7 @@
let selected_image = preview && value?.length ? 0 : null;
let old_selected_image: number | null = selected_image;
- $: if (prevValue !== value) {
+ $: if (!dequal(prevValue, value)) {
// When value is falsy (clear button or first load),
// preview determines the selected image
if (was_reset) {
@@ -69,6 +70,7 @@
? selected_image
: null;
}
+ dispatch("change");
prevValue = value;
}
@@ -160,10 +162,12 @@
container_width / 2 +
container_element.scrollLeft;
- container_element?.scrollTo({
- left: pos < 0 ? 0 : pos,
- behavior: "smooth"
- });
+ if (container_element && typeof container_element.scrollTo === "function") {
+ container_element.scrollTo({
+ left: pos < 0 ? 0 : pos,
+ behavior: "smooth"
+ });
+ }
}
let client_height = 0;
diff --git a/js/gallery/static/StaticGallery.svelte b/js/gallery/static/StaticGallery.svelte
index 0ede91593d..b9a9b8e5db 100644
--- a/js/gallery/static/StaticGallery.svelte
+++ b/js/gallery/static/StaticGallery.svelte
@@ -29,6 +29,7 @@
export let show_share_button = false;
export let show_download_button = false;
export let gradio: Gradio<{
+ change: typeof value;
select: SelectData;
share: ShareData;
error: string;
@@ -49,6 +50,7 @@
>
gradio.dispatch("change", value)}
on:select={(e) => gradio.dispatch("select", e.detail)}
on:share={(e) => gradio.dispatch("share", e.detail)}
on:error={(e) => gradio.dispatch("error", e.detail)}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 21734c9980..d49441e23b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -828,6 +828,9 @@ importers:
'@gradio/utils':
specifier: workspace:^
version: link:../utils
+ dequal:
+ specifier: ^2.0.2
+ version: 2.0.2
js/group: {}