@gradio/multimodaltextbox 0.5.5 → 0.6.0-beta.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.
@@ -10,7 +10,15 @@
10
10
  import { Upload } from "@gradio/upload";
11
11
  import { Image } from "@gradio/image/shared";
12
12
  import type { FileData, Client } from "@gradio/client";
13
- import { Clear, File, Music, Paperclip, Video, Send } from "@gradio/icons";
13
+ import {
14
+ Clear,
15
+ File,
16
+ Music,
17
+ Paperclip,
18
+ Video,
19
+ Send,
20
+ Square
21
+ } from "@gradio/icons";
14
22
  import type { SelectData } from "@gradio/utils";
15
23
 
16
24
  export let value: { text: string; files: FileData[] } = {
@@ -28,6 +36,7 @@
28
36
  export let container = true;
29
37
  export let max_lines: number;
30
38
  export let submit_btn: string | boolean | null = null;
39
+ export let stop_btn: string | boolean | null = null;
31
40
  export let rtl = false;
32
41
  export let autofocus = false;
33
42
  export let text_align: "left" | "right" | undefined = undefined;
@@ -45,11 +54,13 @@
45
54
  let can_scroll: boolean;
46
55
  let previous_scroll_top = 0;
47
56
  let user_has_scrolled_up = false;
48
- let dragging = false;
57
+ export let dragging = false;
49
58
  let uploading = false;
50
59
  let oldValue = value.text;
51
60
  $: dispatch("drag", dragging);
52
61
 
62
+ let full_container: HTMLDivElement;
63
+
53
64
  $: if (oldValue !== value.text) {
54
65
  dispatch("change", value);
55
66
  oldValue = value.text;
@@ -73,6 +84,7 @@
73
84
  const dispatch = createEventDispatcher<{
74
85
  change: typeof value;
75
86
  submit: undefined;
87
+ stop: undefined;
76
88
  blur: undefined;
77
89
  select: SelectData;
78
90
  input: undefined;
@@ -186,7 +198,11 @@
186
198
  }
187
199
  }
188
200
 
189
- async function handle_submit(): Promise<void> {
201
+ function handle_stop(): void {
202
+ dispatch("stop");
203
+ }
204
+
205
+ function handle_submit(): void {
190
206
  dispatch("submit");
191
207
  }
192
208
 
@@ -201,109 +217,181 @@
201
217
  }
202
218
  }
203
219
  }
220
+
221
+ function handle_dragenter(event: DragEvent): void {
222
+ event.preventDefault();
223
+ dragging = true;
224
+ }
225
+
226
+ function handle_dragleave(event: DragEvent): void {
227
+ event.preventDefault();
228
+ const rect = full_container.getBoundingClientRect();
229
+ const { clientX, clientY } = event;
230
+ if (
231
+ clientX <= rect.left ||
232
+ clientX >= rect.right ||
233
+ clientY <= rect.top ||
234
+ clientY >= rect.bottom
235
+ ) {
236
+ dragging = false;
237
+ }
238
+ }
239
+
240
+ function handle_drop(event: DragEvent): void {
241
+ event.preventDefault();
242
+ dragging = false;
243
+ if (event.dataTransfer && event.dataTransfer.files) {
244
+ upload_component.load_files(Array.from(event.dataTransfer.files));
245
+ }
246
+ }
204
247
  </script>
205
248
 
206
- <!-- svelte-ignore a11y-autofocus -->
207
- <label class:container>
208
- <BlockTitle {show_label} {info}>{label}</BlockTitle>
209
- {#if value.files.length > 0 || uploading}
210
- <div
211
- class="thumbnails scroll-hide"
212
- data-testid="container_el"
213
- style="display: {value.files.length > 0 || uploading ? 'flex' : 'none'};"
214
- >
215
- {#each value.files as file, index}
216
- <button class="thumbnail-item thumbnail-small">
217
- <button
218
- class:disabled
219
- class="delete-button"
220
- on:click={(event) => remove_thumbnail(event, index)}
221
- ><Clear /></button
222
- >
223
- {#if file.mime_type && file.mime_type.includes("image")}
224
- <Image
225
- src={file.url}
226
- title={null}
227
- alt=""
228
- loading="lazy"
229
- class={"thumbnail-image"}
230
- />
231
- {:else if file.mime_type && file.mime_type.includes("audio")}
232
- <Music />
233
- {:else if file.mime_type && file.mime_type.includes("video")}
234
- <Video />
249
+ <div
250
+ class="full-container"
251
+ class:dragging
252
+ bind:this={full_container}
253
+ on:dragenter={handle_dragenter}
254
+ on:dragleave={handle_dragleave}
255
+ on:dragover|preventDefault
256
+ on:drop={handle_drop}
257
+ role="group"
258
+ aria-label="Multimedia input field"
259
+ >
260
+ <!-- svelte-ignore a11y-autofocus -->
261
+ <label class:container>
262
+ <BlockTitle {show_label} {info}>{label}</BlockTitle>
263
+ {#if value.files.length > 0 || uploading}
264
+ <div
265
+ class="thumbnails scroll-hide"
266
+ aria-label="Uploaded files"
267
+ data-testid="container_el"
268
+ style="display: {value.files.length > 0 || uploading
269
+ ? 'flex'
270
+ : 'none'};"
271
+ >
272
+ {#each value.files as file, index}
273
+ <span role="listitem" aria-label="File thumbnail">
274
+ <button class="thumbnail-item thumbnail-small">
275
+ <button
276
+ class:disabled
277
+ class="delete-button"
278
+ on:click={(event) => remove_thumbnail(event, index)}
279
+ ><Clear /></button
280
+ >
281
+ {#if file.mime_type && file.mime_type.includes("image")}
282
+ <Image
283
+ src={file.url}
284
+ title={null}
285
+ alt=""
286
+ loading="lazy"
287
+ class={"thumbnail-image"}
288
+ />
289
+ {:else if file.mime_type && file.mime_type.includes("audio")}
290
+ <Music />
291
+ {:else if file.mime_type && file.mime_type.includes("video")}
292
+ <Video />
293
+ {:else}
294
+ <File />
295
+ {/if}
296
+ </button>
297
+ </span>
298
+ {/each}
299
+ {#if uploading}
300
+ <div class="loader" role="status" aria-label="Uploading"></div>
301
+ {/if}
302
+ </div>
303
+ {/if}
304
+ <div class="input-container">
305
+ <Upload
306
+ bind:this={upload_component}
307
+ on:load={handle_upload}
308
+ {file_count}
309
+ {root}
310
+ {max_file_size}
311
+ bind:dragging
312
+ bind:uploading
313
+ show_progress={false}
314
+ disable_click={true}
315
+ bind:hidden_upload
316
+ on:error
317
+ hidden={true}
318
+ {upload}
319
+ {stream_handler}
320
+ ></Upload>
321
+ <button
322
+ data-testid="upload-button"
323
+ class="upload-button"
324
+ on:click={handle_upload_click}><Paperclip /></button
325
+ >
326
+ <textarea
327
+ data-testid="textbox"
328
+ use:text_area_resize={{
329
+ text: value.text,
330
+ lines: lines,
331
+ max_lines: max_lines
332
+ }}
333
+ class="scroll-hide"
334
+ dir={rtl ? "rtl" : "ltr"}
335
+ bind:value={value.text}
336
+ bind:this={el}
337
+ {placeholder}
338
+ rows={lines}
339
+ {disabled}
340
+ {autofocus}
341
+ on:keypress={handle_keypress}
342
+ on:blur
343
+ on:select={handle_select}
344
+ on:focus
345
+ on:scroll={handle_scroll}
346
+ on:paste={handle_paste}
347
+ style={text_align ? "text-align: " + text_align : ""}
348
+ />
349
+ {#if submit_btn}
350
+ <button
351
+ class="submit-button"
352
+ class:padded-button={submit_btn !== true}
353
+ on:click={handle_submit}
354
+ >
355
+ {#if submit_btn === true}
356
+ <Send />
235
357
  {:else}
236
- <File />
358
+ {submit_btn}
359
+ {/if}
360
+ </button>
361
+ {/if}
362
+ {#if stop_btn}
363
+ <button
364
+ class="stop-button"
365
+ class:padded-button={stop_btn !== true}
366
+ on:click={handle_stop}
367
+ >
368
+ {#if stop_btn === true}
369
+ <Square />
370
+ {:else}
371
+ {stop_btn}
237
372
  {/if}
238
373
  </button>
239
- {/each}
240
- {#if uploading}
241
- <div class="loader"></div>
242
374
  {/if}
243
375
  </div>
244
- {/if}
245
- <div class="input-container">
246
- <Upload
247
- bind:this={upload_component}
248
- on:load={handle_upload}
249
- {file_count}
250
- {root}
251
- {max_file_size}
252
- bind:dragging
253
- bind:uploading
254
- show_progress={false}
255
- disable_click={true}
256
- bind:hidden_upload
257
- on:error
258
- hidden={true}
259
- {upload}
260
- {stream_handler}
261
- ></Upload>
262
- <button
263
- data-testid="upload-button"
264
- class="upload-button"
265
- on:click={handle_upload_click}><Paperclip /></button
266
- >
267
- <textarea
268
- data-testid="textbox"
269
- use:text_area_resize={{
270
- text: value.text,
271
- lines: lines,
272
- max_lines: max_lines
273
- }}
274
- class="scroll-hide"
275
- dir={rtl ? "rtl" : "ltr"}
276
- bind:value={value.text}
277
- bind:this={el}
278
- {placeholder}
279
- rows={lines}
280
- {disabled}
281
- {autofocus}
282
- on:keypress={handle_keypress}
283
- on:blur
284
- on:select={handle_select}
285
- on:focus
286
- on:scroll={handle_scroll}
287
- on:paste={handle_paste}
288
- style={text_align ? "text-align: " + text_align : ""}
289
- />
290
- {#if submit_btn}
291
- <button
292
- class="submit-button"
293
- class:padded-button={submit_btn !== true}
294
- on:click={handle_submit}
295
- >
296
- {#if submit_btn === true}
297
- <Send />
298
- {:else}
299
- Hello World
300
- {/if}
301
- </button>
302
- {/if}
303
- </div>
304
- </label>
376
+ </label>
377
+ </div>
305
378
 
306
379
  <style>
380
+ .full-container {
381
+ width: 100%;
382
+ position: relative;
383
+ }
384
+
385
+ .full-container.dragging::after {
386
+ content: "";
387
+ position: absolute;
388
+ top: 0;
389
+ left: 0;
390
+ right: 0;
391
+ bottom: 0;
392
+ pointer-events: none;
393
+ }
394
+
307
395
  .input-container {
308
396
  display: flex;
309
397
  position: relative;
@@ -323,6 +411,8 @@
323
411
  margin-top: 0px;
324
412
  margin-bottom: 0px;
325
413
  resize: none;
414
+ position: relative;
415
+ z-index: 1;
326
416
  }
327
417
 
328
418
  textarea:disabled {
@@ -335,9 +425,8 @@
335
425
  }
336
426
 
337
427
  .upload-button,
338
- .submit-button {
339
- background: var(--button-secondary-background-fill);
340
- color: var(--button-secondary-text-color);
428
+ .submit-button,
429
+ .stop-button {
341
430
  border: none;
342
431
  text-align: center;
343
432
  text-decoration: none;
@@ -351,11 +440,18 @@
351
440
  justify-content: center;
352
441
  align-items: center;
353
442
  margin-bottom: 5px;
443
+ z-index: var(--layer-1);
354
444
  }
355
445
  .padded-button {
356
446
  padding: 0 10px;
357
447
  }
358
448
 
449
+ .upload-button,
450
+ .submit-button {
451
+ background: var(--button-secondary-background-fill);
452
+ color: var(--button-secondary-text-color);
453
+ }
454
+
359
455
  .upload-button:hover,
360
456
  .submit-button:hover {
361
457
  background: var(--button-secondary-background-fill-hover);
@@ -375,6 +471,19 @@
375
471
  width: 17px;
376
472
  }
377
473
 
474
+ .stop-button {
475
+ background: var(--button-cancel-background-fill);
476
+ color: var(--button-cancel-text-color);
477
+ }
478
+ .stop-button:hover {
479
+ background: var(--button-cancel-background-fill-hover);
480
+ color: var(--button-cancel-text-color-hover);
481
+ }
482
+ .stop-button :global(svg) {
483
+ height: 18px;
484
+ width: 18px;
485
+ }
486
+
378
487
  .loader {
379
488
  display: flex;
380
489
  justify-content: center;
@@ -406,11 +515,11 @@
406
515
  }
407
516
 
408
517
  .thumbnails {
409
- align-self: flex-start;
410
518
  display: flex;
411
- justify-content: left;
412
519
  align-items: center;
413
520
  gap: var(--spacing-lg);
521
+ overflow-x: scroll;
522
+ padding-top: var(--spacing-sm);
414
523
  }
415
524
 
416
525
  .thumbnail-item {
package/shared/utils.ts CHANGED
@@ -42,17 +42,16 @@ export function text_area_resize(
42
42
  ): any | undefined {
43
43
  if (_value.lines === _value.max_lines) return;
44
44
  _el.style.overflowY = "scroll";
45
- _el.addEventListener("input", (event: Event) =>
46
- resize(event.target as HTMLTextAreaElement, _value.lines, _value.max_lines)
47
- );
45
+
46
+ function handle_input(event: Event): void {
47
+ resize(event.target as HTMLTextAreaElement, _value.lines, _value.max_lines);
48
+ }
49
+ _el.addEventListener("input", handle_input);
48
50
 
49
51
  if (!_value.text.trim()) return;
50
52
  resize(_el, _value.lines, _value.max_lines);
51
53
 
52
54
  return {
53
- destroy: () =>
54
- _el.removeEventListener("input", (e: Event) =>
55
- resize(e.target as HTMLTextAreaElement, _value.lines, _value.max_lines)
56
- )
55
+ destroy: () => _el.removeEventListener("input", handle_input)
57
56
  };
58
57
  }