@gradio/downloadbutton 0.4.18 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/DownloadButton.stories.svelte +0 -10
- package/DownloadButton.test.ts +418 -0
- package/Index.svelte +1 -0
- package/dist/Index.svelte +1 -0
- package/dist/types.d.ts +1 -0
- package/package.json +5 -5
- package/types.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# @gradio/downloadbutton
|
|
2
2
|
|
|
3
|
+
## 0.5.1
|
|
4
|
+
|
|
5
|
+
### Dependency updates
|
|
6
|
+
|
|
7
|
+
- @gradio/utils@0.13.0
|
|
8
|
+
- @gradio/client@2.3.0
|
|
9
|
+
- @gradio/button@0.8.0
|
|
10
|
+
|
|
11
|
+
## 0.5.0
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- [#13502](https://github.com/gradio-app/gradio/pull/13502) [`429faeb`](https://github.com/gradio-app/gradio/commit/429faeb643fb1afc1722c0f63fafa11603f2c87f) - Ensure every component dispatches a `change` event when its value changes. Thanks @abidlabs!
|
|
16
|
+
|
|
17
|
+
### Dependency updates
|
|
18
|
+
|
|
19
|
+
- @gradio/client@2.2.2
|
|
20
|
+
- @gradio/button@0.7.0
|
|
21
|
+
|
|
22
|
+
## 0.4.18
|
|
23
|
+
|
|
24
|
+
### Dependency updates
|
|
25
|
+
|
|
26
|
+
- @gradio/client@2.2.1
|
|
27
|
+
|
|
28
|
+
## 0.4.18
|
|
29
|
+
|
|
30
|
+
### Dependency updates
|
|
31
|
+
|
|
32
|
+
- @gradio/client@2.2.0
|
|
33
|
+
|
|
34
|
+
## 0.4.18
|
|
35
|
+
|
|
36
|
+
### Dependency updates
|
|
37
|
+
|
|
38
|
+
- @gradio/utils@0.12.2
|
|
39
|
+
|
|
40
|
+
## 0.4.18
|
|
41
|
+
|
|
42
|
+
### Dependency updates
|
|
43
|
+
|
|
44
|
+
- @gradio/utils@0.12.1
|
|
45
|
+
- @gradio/button@0.6.6
|
|
46
|
+
|
|
3
47
|
## 0.4.18
|
|
4
48
|
|
|
5
49
|
### Dependency updates
|
|
@@ -65,11 +65,6 @@
|
|
|
65
65
|
args={{ label: "Download", variant: "secondary", size: "lg" }}
|
|
66
66
|
{template}
|
|
67
67
|
/>
|
|
68
|
-
<Story
|
|
69
|
-
name="Stop"
|
|
70
|
-
args={{ label: "Download", variant: "stop", size: "lg" }}
|
|
71
|
-
{template}
|
|
72
|
-
/>
|
|
73
68
|
<Story
|
|
74
69
|
name="Button with external image icon"
|
|
75
70
|
args={{
|
|
@@ -81,11 +76,6 @@
|
|
|
81
76
|
}}
|
|
82
77
|
{template}
|
|
83
78
|
/>
|
|
84
|
-
<Story
|
|
85
|
-
name="Button with visible equal to false"
|
|
86
|
-
args={{ label: "Download", visible: false }}
|
|
87
|
-
{template}
|
|
88
|
-
/>
|
|
89
79
|
<Story
|
|
90
80
|
name="Button with null label (should be collapsed)"
|
|
91
81
|
args={{ label: null }}
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { test, describe, afterEach, beforeEach, expect, vi } from "vitest";
|
|
2
|
+
import { cleanup, render, fireEvent } from "@self/tootils/render";
|
|
3
|
+
import { run_shared_prop_tests } from "@self/tootils/shared-prop-tests";
|
|
4
|
+
|
|
5
|
+
import DownloadButton from "./Index.svelte";
|
|
6
|
+
|
|
7
|
+
const default_props = {
|
|
8
|
+
value: null,
|
|
9
|
+
variant: "secondary" as const,
|
|
10
|
+
size: "lg" as const,
|
|
11
|
+
icon: null,
|
|
12
|
+
label: "Download",
|
|
13
|
+
interactive: true
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
run_shared_prop_tests({
|
|
17
|
+
component: DownloadButton,
|
|
18
|
+
name: "DownloadButton",
|
|
19
|
+
base_props: { ...default_props },
|
|
20
|
+
has_label: false,
|
|
21
|
+
has_validation_error: false,
|
|
22
|
+
visible_false_hides: true,
|
|
23
|
+
has_block_wrapper: false
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function spy_anchor_click(): {
|
|
27
|
+
captured: { href: string; download: string }[];
|
|
28
|
+
restore: () => void;
|
|
29
|
+
} {
|
|
30
|
+
const captured: { href: string; download: string }[] = [];
|
|
31
|
+
const spy = vi
|
|
32
|
+
.spyOn(HTMLAnchorElement.prototype, "click")
|
|
33
|
+
.mockImplementation(function (this: HTMLAnchorElement) {
|
|
34
|
+
captured.push({ href: this.href, download: this.download });
|
|
35
|
+
});
|
|
36
|
+
return { captured, restore: () => spy.mockRestore() };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("Props: label", () => {
|
|
40
|
+
afterEach(() => cleanup());
|
|
41
|
+
|
|
42
|
+
test("renders the label as the button's accessible name", async () => {
|
|
43
|
+
const { getByRole } = await render(DownloadButton, {
|
|
44
|
+
...default_props,
|
|
45
|
+
label: "download CSV"
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(getByRole("button", { name: "download CSV" })).toBeVisible();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("null label renders an empty button", async () => {
|
|
52
|
+
const { getByRole } = await render(DownloadButton, {
|
|
53
|
+
...default_props,
|
|
54
|
+
label: null
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(getByRole("button")).toBeVisible();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("Props: icon", () => {
|
|
62
|
+
afterEach(() => cleanup());
|
|
63
|
+
|
|
64
|
+
test("renders icon image when icon prop is set", async () => {
|
|
65
|
+
const { getByRole } = await render(DownloadButton, {
|
|
66
|
+
...default_props,
|
|
67
|
+
icon: {
|
|
68
|
+
url: "https://gradio.app/icon.svg",
|
|
69
|
+
path: "icon.svg",
|
|
70
|
+
orig_name: "icon.svg"
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const img = getByRole("img");
|
|
75
|
+
expect(img).toHaveAttribute("src", "https://gradio.app/icon.svg");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("does not render icon image when icon prop is null", async () => {
|
|
79
|
+
const { queryByRole } = await render(DownloadButton, {
|
|
80
|
+
...default_props,
|
|
81
|
+
icon: null
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(queryByRole("img")).not.toBeInTheDocument();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("Props: value", () => {
|
|
89
|
+
let anchor_spy: ReturnType<typeof spy_anchor_click>;
|
|
90
|
+
|
|
91
|
+
beforeEach(() => {
|
|
92
|
+
anchor_spy = spy_anchor_click();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
afterEach(() => {
|
|
96
|
+
anchor_spy.restore();
|
|
97
|
+
cleanup();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("clicking triggers a synthetic anchor click with the file URL", async () => {
|
|
101
|
+
const { getByRole } = await render(DownloadButton, {
|
|
102
|
+
...default_props,
|
|
103
|
+
value: {
|
|
104
|
+
url: "https://gradio.app/files/report.pdf",
|
|
105
|
+
orig_name: "report.pdf",
|
|
106
|
+
path: "report.pdf"
|
|
107
|
+
} as any
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await fireEvent.click(getByRole("button", { name: "Download" }));
|
|
111
|
+
|
|
112
|
+
expect(anchor_spy.captured).toHaveLength(1);
|
|
113
|
+
expect(anchor_spy.captured[0].href).toBe(
|
|
114
|
+
"https://gradio.app/files/report.pdf"
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("uses orig_name as the download filename when provided", async () => {
|
|
119
|
+
const { getByRole } = await render(DownloadButton, {
|
|
120
|
+
...default_props,
|
|
121
|
+
value: {
|
|
122
|
+
url: "https://gradio.app/abc123",
|
|
123
|
+
orig_name: "weekly-downloads.pdf",
|
|
124
|
+
path: "abc123"
|
|
125
|
+
} as any
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await fireEvent.click(getByRole("button", { name: "Download" }));
|
|
129
|
+
|
|
130
|
+
expect(anchor_spy.captured[0].download).toBe("weekly-downloads.pdf");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("derives filename from URL when orig_name is missing", async () => {
|
|
134
|
+
const { getByRole } = await render(DownloadButton, {
|
|
135
|
+
...default_props,
|
|
136
|
+
value: {
|
|
137
|
+
url: "https://gradio.app/files/data.csv",
|
|
138
|
+
orig_name: "",
|
|
139
|
+
path: "files/data.csv"
|
|
140
|
+
} as any
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await fireEvent.click(getByRole("button", { name: "Download" }));
|
|
144
|
+
|
|
145
|
+
expect(anchor_spy.captured[0].download).toBe("data.csv");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("strips query string from URL-derived filename", async () => {
|
|
149
|
+
const { getByRole } = await render(DownloadButton, {
|
|
150
|
+
...default_props,
|
|
151
|
+
value: {
|
|
152
|
+
url: "https://gradio.app/files/data.csv?token=abc&v=2",
|
|
153
|
+
orig_name: "",
|
|
154
|
+
path: "files/data.csv"
|
|
155
|
+
} as any
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await fireEvent.click(getByRole("button", { name: "Download" }));
|
|
159
|
+
|
|
160
|
+
expect(anchor_spy.captured[0].download).toBe("data.csv");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("strips hash fragment from URL-derived filename", async () => {
|
|
164
|
+
const { getByRole } = await render(DownloadButton, {
|
|
165
|
+
...default_props,
|
|
166
|
+
value: {
|
|
167
|
+
url: "https://gradio.app/files/notes.md#section-2",
|
|
168
|
+
orig_name: "",
|
|
169
|
+
path: "files/notes.md"
|
|
170
|
+
} as any
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
await fireEvent.click(getByRole("button", { name: "Download" }));
|
|
174
|
+
|
|
175
|
+
expect(anchor_spy.captured[0].download).toBe("notes.md");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("does not trigger download when value is null", async () => {
|
|
179
|
+
const { getByRole } = await render(DownloadButton, {
|
|
180
|
+
...default_props,
|
|
181
|
+
value: null
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
await fireEvent.click(getByRole("button", { name: "Download" }));
|
|
185
|
+
|
|
186
|
+
expect(anchor_spy.captured).toHaveLength(0);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("does not trigger download when value.url is missing", async () => {
|
|
190
|
+
const { getByRole } = await render(DownloadButton, {
|
|
191
|
+
...default_props,
|
|
192
|
+
value: { url: "", orig_name: "file.txt", path: "file.txt" } as any
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await fireEvent.click(getByRole("button", { name: "Download" }));
|
|
196
|
+
|
|
197
|
+
expect(anchor_spy.captured).toHaveLength(0);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("Props: disabled", () => {
|
|
202
|
+
afterEach(() => cleanup());
|
|
203
|
+
|
|
204
|
+
test("interactive=false renders the button as disabled", async () => {
|
|
205
|
+
const { getByRole } = await render(DownloadButton, {
|
|
206
|
+
...default_props,
|
|
207
|
+
interactive: false,
|
|
208
|
+
value: {
|
|
209
|
+
url: "https://gradio.app/files/report.pdf",
|
|
210
|
+
orig_name: "report.pdf",
|
|
211
|
+
path: "report.pdf"
|
|
212
|
+
} as any
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(getByRole("button", { name: "Download" })).toBeDisabled();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("interactive=true renders the button as enabled", async () => {
|
|
219
|
+
const { getByRole } = await render(DownloadButton, {
|
|
220
|
+
...default_props,
|
|
221
|
+
interactive: true
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
expect(getByRole("button", { name: "Download" })).toBeEnabled();
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe("get_data / set_data", () => {
|
|
229
|
+
let anchor_spy: ReturnType<typeof spy_anchor_click>;
|
|
230
|
+
|
|
231
|
+
beforeEach(() => {
|
|
232
|
+
anchor_spy = spy_anchor_click();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
afterEach(() => {
|
|
236
|
+
anchor_spy.restore();
|
|
237
|
+
cleanup();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("get_data returns the current value", async () => {
|
|
241
|
+
const file_value = {
|
|
242
|
+
url: "https://gradio.app/files/report.pdf",
|
|
243
|
+
orig_name: "report.pdf",
|
|
244
|
+
path: "report.pdf"
|
|
245
|
+
};
|
|
246
|
+
const { get_data } = await render(DownloadButton, {
|
|
247
|
+
...default_props,
|
|
248
|
+
value: file_value as any
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
expect((await get_data()).value).toEqual(file_value);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("get_data returns null when no value is set", async () => {
|
|
255
|
+
const { get_data } = await render(DownloadButton, {
|
|
256
|
+
...default_props,
|
|
257
|
+
value: null
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
expect((await get_data()).value).toBeNull();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("set_data updates the value used for download", async () => {
|
|
264
|
+
const { getByRole, set_data } = await render(DownloadButton, {
|
|
265
|
+
...default_props,
|
|
266
|
+
value: null
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
await set_data({
|
|
270
|
+
value: {
|
|
271
|
+
url: "https://gradio.app/files/updated.pdf",
|
|
272
|
+
orig_name: "updated.pdf",
|
|
273
|
+
path: "updated.pdf"
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
await fireEvent.click(getByRole("button", { name: "Download" }));
|
|
278
|
+
|
|
279
|
+
expect(anchor_spy.captured).toHaveLength(1);
|
|
280
|
+
expect(anchor_spy.captured[0].href).toBe(
|
|
281
|
+
"https://gradio.app/files/updated.pdf"
|
|
282
|
+
);
|
|
283
|
+
expect(anchor_spy.captured[0].download).toBe("updated.pdf");
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("set_data then get_data round-trips the value", async () => {
|
|
287
|
+
const { get_data, set_data } = await render(DownloadButton, {
|
|
288
|
+
...default_props,
|
|
289
|
+
value: null
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const new_value = {
|
|
293
|
+
url: "https://gradio.app/files/data.csv",
|
|
294
|
+
orig_name: "data.csv",
|
|
295
|
+
path: "data.csv"
|
|
296
|
+
};
|
|
297
|
+
await set_data({ value: new_value });
|
|
298
|
+
|
|
299
|
+
expect((await get_data()).value).toEqual(new_value);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describe("Events", () => {
|
|
304
|
+
let anchor_spy: ReturnType<typeof spy_anchor_click>;
|
|
305
|
+
|
|
306
|
+
beforeEach(() => {
|
|
307
|
+
anchor_spy = spy_anchor_click();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
afterEach(() => {
|
|
311
|
+
anchor_spy.restore();
|
|
312
|
+
cleanup();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("does not dispatch spurious click event on mount", async () => {
|
|
316
|
+
const { listen } = await render(DownloadButton, {
|
|
317
|
+
...default_props,
|
|
318
|
+
value: {
|
|
319
|
+
url: "https://gradio.app/file.zip",
|
|
320
|
+
orig_name: "file.zip",
|
|
321
|
+
path: "file.zip"
|
|
322
|
+
} as any
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const click = listen("click", { retrospective: true });
|
|
326
|
+
expect(click).not.toHaveBeenCalled();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("dispatches click event with a downloadable value", async () => {
|
|
330
|
+
const { getByRole, listen } = await render(DownloadButton, {
|
|
331
|
+
...default_props,
|
|
332
|
+
value: {
|
|
333
|
+
url: "https://gradio.app/file.zip",
|
|
334
|
+
orig_name: "file.zip",
|
|
335
|
+
path: "file.zip"
|
|
336
|
+
} as any
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const click = listen("click");
|
|
340
|
+
await fireEvent.click(getByRole("button", { name: "Download" }));
|
|
341
|
+
|
|
342
|
+
expect(click).toHaveBeenCalledTimes(1);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("dispatches click event even when value is null", async () => {
|
|
346
|
+
const { getByRole, listen } = await render(DownloadButton, {
|
|
347
|
+
...default_props,
|
|
348
|
+
value: null
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const click = listen("click");
|
|
352
|
+
await fireEvent.click(getByRole("button", { name: "Download" }));
|
|
353
|
+
|
|
354
|
+
expect(click).toHaveBeenCalledTimes(1);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
describe("Edge cases", () => {
|
|
359
|
+
let anchor_spy: ReturnType<typeof spy_anchor_click>;
|
|
360
|
+
|
|
361
|
+
beforeEach(() => {
|
|
362
|
+
anchor_spy = spy_anchor_click();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
afterEach(() => {
|
|
366
|
+
anchor_spy.restore();
|
|
367
|
+
cleanup();
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("falls back to 'file' when orig_name is empty and URL ends with a trailing slash", async () => {
|
|
371
|
+
const { getByRole } = await render(DownloadButton, {
|
|
372
|
+
...default_props,
|
|
373
|
+
value: {
|
|
374
|
+
url: "https://gradio.app/files/",
|
|
375
|
+
orig_name: "",
|
|
376
|
+
path: "files/"
|
|
377
|
+
} as any
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
await fireEvent.click(getByRole("button", { name: "Download" }));
|
|
381
|
+
|
|
382
|
+
expect(anchor_spy.captured).toHaveLength(1);
|
|
383
|
+
expect(anchor_spy.captured[0].download).toBe("file");
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("falls back to 'file' when URL has no path segments after domain", async () => {
|
|
387
|
+
const { getByRole } = await render(DownloadButton, {
|
|
388
|
+
...default_props,
|
|
389
|
+
value: {
|
|
390
|
+
url: "https://gradio.app/",
|
|
391
|
+
orig_name: "",
|
|
392
|
+
path: ""
|
|
393
|
+
} as any
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
await fireEvent.click(getByRole("button", { name: "Download" }));
|
|
397
|
+
|
|
398
|
+
expect(anchor_spy.captured).toHaveLength(1);
|
|
399
|
+
expect(anchor_spy.captured[0].download).toBe("file");
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("does not leak anchor elements into document.body after download", async () => {
|
|
403
|
+
const anchors_before = document.body.querySelectorAll("a").length;
|
|
404
|
+
|
|
405
|
+
const { getByRole } = await render(DownloadButton, {
|
|
406
|
+
...default_props,
|
|
407
|
+
value: {
|
|
408
|
+
url: "https://gradio.app/files/report.pdf",
|
|
409
|
+
orig_name: "report.pdf",
|
|
410
|
+
path: "report.pdf"
|
|
411
|
+
} as any
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
await fireEvent.click(getByRole("button", { name: "Download" }));
|
|
415
|
+
|
|
416
|
+
expect(document.body.querySelectorAll("a").length).toBe(anchors_before);
|
|
417
|
+
});
|
|
418
|
+
});
|
package/Index.svelte
CHANGED
package/dist/Index.svelte
CHANGED
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/downloadbutton",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Gradio UI packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
7
7
|
"license": "ISC",
|
|
8
8
|
"private": false,
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@gradio/button": "^0.
|
|
11
|
-
"@gradio/
|
|
12
|
-
"@gradio/
|
|
10
|
+
"@gradio/button": "^0.8.0",
|
|
11
|
+
"@gradio/client": "^2.3.0",
|
|
12
|
+
"@gradio/utils": "^0.13.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@gradio/preview": "^0.16.
|
|
15
|
+
"@gradio/preview": "^0.16.2"
|
|
16
16
|
},
|
|
17
17
|
"main": "./Index.svelte",
|
|
18
18
|
"main_changeset": true,
|