@gradio/dataframe 0.11.0-beta.7 → 0.11.0

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.
@@ -3,10 +3,10 @@ import { dsvFormat } from "d3-dsv";
3
3
  import { dequal } from "dequal/lite";
4
4
  import { copy } from "@gradio/utils";
5
5
  import { Upload } from "@gradio/upload";
6
- import { BaseButton } from "@gradio/button";
7
6
  import EditableCell from "./EditableCell.svelte";
8
7
  import {} from "@gradio/client";
9
8
  import VirtualTable from "./VirtualTable.svelte";
9
+ import CellMenu from "./CellMenu.svelte";
10
10
  export let datatype;
11
11
  export let label = null;
12
12
  export let show_label = true;
@@ -34,7 +34,7 @@ const get_data_at = (row, col) => data?.[row]?.[col]?.value;
34
34
  $: {
35
35
  if (selected !== false) {
36
36
  const [row, col] = selected;
37
- if (!isNaN(row) && !isNaN(col)) {
37
+ if (!isNaN(row) && !isNaN(col) && data[row]) {
38
38
  dispatch("select", {
39
39
  index: [row, col],
40
40
  value: get_data_at(row, col),
@@ -250,7 +250,13 @@ async function handle_keydown(event) {
250
250
  }
251
251
  }
252
252
  }
253
+ let active_cell = null;
253
254
  async function handle_cell_click(i, j) {
255
+ if (active_cell && active_cell.row === i && active_cell.col === j) {
256
+ active_cell = null;
257
+ } else {
258
+ active_cell = { row: i, col: j };
259
+ }
254
260
  if (dequal(editing, [i, j]))
255
261
  return;
256
262
  header_edit = false;
@@ -308,40 +314,46 @@ async function add_row(index) {
308
314
  values = [Array(headers.length).fill("")];
309
315
  return;
310
316
  }
311
- data.splice(
312
- index ? index + 1 : data.length,
313
- 0,
314
- Array(data[0].length).fill(0).map((_, i) => {
315
- const _id = make_id();
316
- els[_id] = { cell: null, input: null };
317
- return { id: _id, value: "" };
318
- })
319
- );
317
+ const new_row = Array(data[0].length).fill(0).map((_, i) => {
318
+ const _id = make_id();
319
+ els[_id] = { cell: null, input: null };
320
+ return { id: _id, value: "" };
321
+ });
322
+ if (index !== void 0 && index >= 0 && index <= data.length) {
323
+ data.splice(index, 0, new_row);
324
+ } else {
325
+ data.push(new_row);
326
+ }
320
327
  data = data;
321
- selected = [index ? index + 1 : data.length - 1, 0];
328
+ selected = [index !== void 0 ? index : data.length - 1, 0];
322
329
  }
323
330
  $:
324
331
  (data || selected_header) && trigger_change();
325
- async function add_col() {
332
+ async function add_col(index) {
326
333
  parent.focus();
327
334
  if (col_count[1] !== "dynamic")
328
335
  return;
336
+ const insert_index = index !== void 0 ? index : data[0].length;
329
337
  for (let i = 0; i < data.length; i++) {
330
338
  const _id = make_id();
331
339
  els[_id] = { cell: null, input: null };
332
- data[i].push({ id: _id, value: "" });
340
+ data[i].splice(insert_index, 0, { id: _id, value: "" });
333
341
  }
334
- headers.push(`Header ${headers.length + 1}`);
342
+ headers.splice(insert_index, 0, `Header ${headers.length + 1}`);
335
343
  data = data;
336
344
  headers = headers;
337
345
  await tick();
338
346
  requestAnimationFrame(() => {
339
- edit_header(headers.length - 1, true);
347
+ edit_header(insert_index, true);
340
348
  const new_w = parent.querySelectorAll("tbody")[1].offsetWidth;
341
349
  parent.querySelectorAll("table")[1].scrollTo({ left: new_w });
342
350
  });
343
351
  }
344
352
  function handle_click_outside(event) {
353
+ if (active_cell_menu && !event.target.closest(".cell-menu") || active_header_menu && !event.target.closest(".cell-menu")) {
354
+ active_cell_menu = null;
355
+ active_header_menu = null;
356
+ }
345
357
  event.stopImmediatePropagation();
346
358
  const [trigger] = event.composedPath();
347
359
  if (parent.contains(trigger)) {
@@ -351,6 +363,9 @@ function handle_click_outside(event) {
351
363
  header_edit = false;
352
364
  selected_header = false;
353
365
  selected = false;
366
+ active_cell = null;
367
+ active_cell_menu = null;
368
+ active_header_menu = null;
354
369
  }
355
370
  function guess_delimitaor(text, possibleDelimiters) {
356
371
  return possibleDelimiters.filter(weedOut);
@@ -485,6 +500,75 @@ onMount(() => {
485
500
  observer.disconnect();
486
501
  };
487
502
  });
503
+ let highlighted_column = null;
504
+ let active_cell_menu = null;
505
+ function toggle_cell_menu(event, row, col) {
506
+ event.stopPropagation();
507
+ if (active_cell_menu && active_cell_menu.row === row && active_cell_menu.col === col) {
508
+ active_cell_menu = null;
509
+ } else {
510
+ const cell = event.target.closest("td");
511
+ if (cell) {
512
+ const rect = cell.getBoundingClientRect();
513
+ active_cell_menu = {
514
+ row,
515
+ col,
516
+ x: rect.right,
517
+ y: rect.bottom
518
+ };
519
+ }
520
+ }
521
+ }
522
+ function add_row_at(index, position) {
523
+ const row_index = position === "above" ? index : index + 1;
524
+ add_row(row_index);
525
+ active_cell_menu = null;
526
+ active_header_menu = null;
527
+ }
528
+ function add_col_at(index, position) {
529
+ const col_index = position === "left" ? index : index + 1;
530
+ add_col(col_index);
531
+ active_cell_menu = null;
532
+ active_header_menu = null;
533
+ }
534
+ onMount(() => {
535
+ document.addEventListener("click", handle_click_outside);
536
+ return () => {
537
+ document.removeEventListener("click", handle_click_outside);
538
+ };
539
+ });
540
+ let active_button = null;
541
+ function toggle_header_button(col) {
542
+ if (active_button?.type === "header" && active_button.col === col) {
543
+ active_button = null;
544
+ } else {
545
+ active_button = { type: "header", col };
546
+ }
547
+ }
548
+ function toggle_cell_button(row, col) {
549
+ if (active_button?.type === "cell" && active_button.row === row && active_button.col === col) {
550
+ active_button = null;
551
+ } else {
552
+ active_button = { type: "cell", row, col };
553
+ }
554
+ }
555
+ let active_header_menu = null;
556
+ function toggle_header_menu(event, col) {
557
+ event.stopPropagation();
558
+ if (active_header_menu && active_header_menu.col === col) {
559
+ active_header_menu = null;
560
+ } else {
561
+ const header = event.target.closest("th");
562
+ if (header) {
563
+ const rect = header.getBoundingClientRect();
564
+ active_header_menu = {
565
+ col,
566
+ x: rect.right,
567
+ y: rect.bottom
568
+ };
569
+ }
570
+ }
571
+ }
488
572
  </script>
489
573
 
490
574
  <svelte:window
@@ -603,40 +687,58 @@ onMount(() => {
603
687
  class:focus={header_edit === i || selected_header === i}
604
688
  aria-sort={get_sort_status(value, sort_by, sort_direction)}
605
689
  style="width: var(--cell-width-{i});"
690
+ on:click={() => {
691
+ toggle_header_button(i);
692
+ }}
606
693
  >
607
694
  <div class="cell-wrap">
608
- <EditableCell
609
- bind:value={_headers[i].value}
610
- bind:el={els[id].input}
611
- {latex_delimiters}
612
- {line_breaks}
613
- edit={header_edit === i}
614
- on:keydown={end_header_edit}
615
- on:dblclick={() => edit_header(i)}
616
- {select_on_focus}
617
- header
618
- {root}
619
- />
620
-
621
- <!-- TODO: fix -->
622
- <!-- svelte-ignore a11y-click-events-have-key-events -->
623
- <!-- svelte-ignore a11y-no-static-element-interactions-->
624
- <div
625
- class:sorted={sort_by === i}
626
- class:des={sort_by === i && sort_direction === "des"}
627
- class="sort-button {sort_direction} "
628
- on:click={() => handle_sort(i)}
629
- >
630
- <svg
631
- width="1em"
632
- height="1em"
633
- viewBox="0 0 9 7"
634
- fill="none"
635
- xmlns="http://www.w3.org/2000/svg"
695
+ <div class="header-content">
696
+ <EditableCell
697
+ bind:value={_headers[i].value}
698
+ bind:el={els[id].input}
699
+ {latex_delimiters}
700
+ {line_breaks}
701
+ edit={header_edit === i}
702
+ on:keydown={end_header_edit}
703
+ on:dblclick={() => edit_header(i)}
704
+ {select_on_focus}
705
+ header
706
+ {root}
707
+ />
708
+ <!-- TODO: fix -->
709
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
710
+ <!-- svelte-ignore a11y-no-static-element-interactions-->
711
+ <div
712
+ class:sorted={sort_by === i}
713
+ class:des={sort_by === i && sort_direction === "des"}
714
+ class="sort-button {sort_direction}"
715
+ on:click={(event) => {
716
+ event.stopPropagation();
717
+ handle_sort(i);
718
+ }}
636
719
  >
637
- <path d="M4.49999 0L8.3971 6.75H0.602875L4.49999 0Z" />
638
- </svg>
720
+ <svg
721
+ width="1em"
722
+ height="1em"
723
+ viewBox="0 0 9 7"
724
+ fill="none"
725
+ xmlns="http://www.w3.org/2000/svg"
726
+ >
727
+ <path d="M4.49999 0L8.3971 6.75H0.602875L4.49999 0Z" />
728
+ </svg>
729
+ </div>
639
730
  </div>
731
+
732
+ {#if editable}
733
+ <button
734
+ class="cell-menu-button"
735
+ class:visible={active_button?.type === "header" &&
736
+ active_button.col === i}
737
+ on:click={(event) => toggle_header_menu(event, i)}
738
+ >
739
+
740
+ </button>
741
+ {/if}
640
742
  </div>
641
743
  </th>
642
744
  {/each}
@@ -647,11 +749,17 @@ onMount(() => {
647
749
  <td
648
750
  tabindex="0"
649
751
  on:touchstart={() => start_edit(index, j)}
650
- on:click={() => handle_cell_click(index, j)}
752
+ on:click={() => {
753
+ handle_cell_click(index, j);
754
+ toggle_cell_button(index, j);
755
+ }}
651
756
  on:dblclick={() => start_edit(index, j)}
652
757
  style:width="var(--cell-width-{j})"
653
758
  style={styling?.[index]?.[j] || ""}
654
759
  class:focus={dequal(selected, [index, j])}
760
+ class:menu-active={active_cell_menu &&
761
+ active_cell_menu.row === index &&
762
+ active_cell_menu.col === j}
655
763
  >
656
764
  <div class="cell-wrap">
657
765
  <EditableCell
@@ -667,6 +775,17 @@ onMount(() => {
667
775
  {clear_on_focus}
668
776
  {root}
669
777
  />
778
+ {#if editable}
779
+ <button
780
+ class="cell-menu-button"
781
+ class:visible={active_button?.type === "cell" &&
782
+ active_button.row === index &&
783
+ active_button.col === j}
784
+ on:click={(event) => toggle_cell_menu(event, index, j)}
785
+ >
786
+
787
+ </button>
788
+ {/if}
670
789
  </div>
671
790
  </td>
672
791
  {/each}
@@ -674,64 +793,35 @@ onMount(() => {
674
793
  </VirtualTable>
675
794
  </Upload>
676
795
  </div>
677
- {#if editable}
678
- <div class="controls-wrap">
679
- {#if row_count[1] === "dynamic"}
680
- <span class="button-wrap">
681
- <BaseButton
682
- variant="secondary"
683
- size="sm"
684
- on:click={(e) => (e.stopPropagation(), add_row())}
685
- >
686
- <svg
687
- xmlns="http://www.w3.org/2000/svg"
688
- xmlns:xlink="http://www.w3.org/1999/xlink"
689
- aria-hidden="true"
690
- role="img"
691
- width="1em"
692
- height="1em"
693
- preserveAspectRatio="xMidYMid meet"
694
- viewBox="0 0 32 32"
695
- >
696
- <path
697
- fill="currentColor"
698
- d="M24.59 16.59L17 24.17V4h-2v20.17l-7.59-7.58L6 18l10 10l10-10l-1.41-1.41z"
699
- />
700
- </svg>
701
- {i18n("dataframe.new_row")}
702
- </BaseButton>
703
- </span>
704
- {/if}
705
- {#if col_count[1] === "dynamic"}
706
- <span class="button-wrap">
707
- <BaseButton
708
- variant="secondary"
709
- size="sm"
710
- on:click={(e) => (e.stopPropagation(), add_col())}
711
- >
712
- <svg
713
- xmlns="http://www.w3.org/2000/svg"
714
- xmlns:xlink="http://www.w3.org/1999/xlink"
715
- aria-hidden="true"
716
- role="img"
717
- width="1em"
718
- height="1em"
719
- preserveAspectRatio="xMidYMid meet"
720
- viewBox="0 0 32 32"
721
- >
722
- <path
723
- fill="currentColor"
724
- d="m18 6l-1.43 1.393L24.15 15H4v2h20.15l-7.58 7.573L18 26l10-10L18 6z"
725
- />
726
- </svg>
727
- {i18n("dataframe.new_column")}
728
- </BaseButton>
729
- </span>
730
- {/if}
731
- </div>
732
- {/if}
733
796
  </div>
734
797
 
798
+ {#if active_cell_menu !== null}
799
+ <CellMenu
800
+ {i18n}
801
+ x={active_cell_menu.x}
802
+ y={active_cell_menu.y}
803
+ col={active_cell_menu.col}
804
+ row={active_cell_menu?.row ?? -1}
805
+ on_add_row_above={() => add_row_at(active_cell_menu?.row ?? -1, "above")}
806
+ on_add_row_below={() => add_row_at(active_cell_menu?.row ?? -1, "below")}
807
+ on_add_column_left={() => add_col_at(active_cell_menu?.col ?? -1, "left")}
808
+ on_add_column_right={() => add_col_at(active_cell_menu?.col ?? -1, "right")}
809
+ />
810
+ {/if}
811
+
812
+ {#if active_header_menu !== null}
813
+ <CellMenu
814
+ {i18n}
815
+ x={active_header_menu.x}
816
+ y={active_header_menu.y}
817
+ col={active_header_menu.col}
818
+ row={-1}
819
+ on_add_column_left={() => add_col_at(active_header_menu?.col ?? -1, "left")}
820
+ on_add_column_right={() =>
821
+ add_col_at(active_header_menu?.col ?? -1, "right")}
822
+ />
823
+ {/if}
824
+
735
825
  <style>
736
826
  .button-wrap:hover svg {
737
827
  color: var(--color-accent);
@@ -832,8 +922,9 @@ onMount(() => {
832
922
  }
833
923
 
834
924
  th.focus,
835
- td.focus {
836
- --ring-color: var(--color-accent);
925
+ td.focus,
926
+ td.menu-active {
927
+ z-index: 1;
837
928
  }
838
929
 
839
930
  tr:last-child td:first-child {
@@ -882,21 +973,22 @@ onMount(() => {
882
973
  }
883
974
 
884
975
  .cell-wrap {
976
+ position: relative;
885
977
  display: flex;
886
978
  align-items: center;
979
+ justify-content: space-between;
887
980
  outline: none;
888
981
  height: var(--size-full);
889
982
  min-height: var(--size-9);
983
+ overflow: hidden;
890
984
  }
891
985
 
892
- .controls-wrap {
986
+ .header-content {
893
987
  display: flex;
894
- justify-content: flex-end;
895
- padding-top: var(--size-2);
896
- }
897
-
898
- .controls-wrap > * + * {
899
- margin-left: var(--size-1);
988
+ align-items: center;
989
+ overflow: hidden;
990
+ flex-grow: 1;
991
+ min-width: 0;
900
992
  }
901
993
 
902
994
  .row_odd {
@@ -910,4 +1002,45 @@ onMount(() => {
910
1002
  table {
911
1003
  border-collapse: separate;
912
1004
  }
1005
+
1006
+ .select-column {
1007
+ width: var(--size-3);
1008
+ text-align: center;
1009
+ padding: var(--size-1);
1010
+ border-right: none;
1011
+ }
1012
+
1013
+ .cell-menu-button {
1014
+ flex-shrink: 0;
1015
+ display: none;
1016
+ background-color: var(--block-background-fill);
1017
+ border: 1px solid var(--border-color-primary);
1018
+ border-radius: var(--block-radius);
1019
+ width: var(--size-5);
1020
+ height: var(--size-5);
1021
+ min-width: var(--size-5);
1022
+ padding: 0;
1023
+ margin-right: var(--spacing-sm);
1024
+ z-index: var(--layer-2);
1025
+ }
1026
+
1027
+ .cell-menu-button:hover {
1028
+ background-color: var(--color-bg-hover);
1029
+ }
1030
+
1031
+ .cell-menu-button.visible {
1032
+ display: flex;
1033
+ align-items: center;
1034
+ justify-content: center;
1035
+ }
1036
+
1037
+ th .cell-wrap {
1038
+ padding-right: var(--spacing-sm);
1039
+ }
1040
+
1041
+ th .header-content {
1042
+ white-space: nowrap;
1043
+ overflow: hidden;
1044
+ text-overflow: ellipsis;
1045
+ }
913
1046
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradio/dataframe",
3
- "version": "0.11.0-beta.7",
3
+ "version": "0.11.0",
4
4
  "description": "Gradio UI packages",
5
5
  "type": "module",
6
6
  "author": "",
@@ -17,13 +17,13 @@
17
17
  "dompurify": "^3.0.3",
18
18
  "katex": "^0.16.7",
19
19
  "marked": "^12.0.0",
20
- "@gradio/atoms": "^0.9.0-beta.4",
21
- "@gradio/button": "^0.3.0-beta.6",
22
- "@gradio/markdown": "^0.10.0-beta.4",
23
- "@gradio/upload": "^0.13.0-beta.6",
24
- "@gradio/utils": "^0.7.0-beta.2",
25
- "@gradio/statustracker": "^0.8.0-beta.4",
26
- "@gradio/client": "^1.6.0-beta.4"
20
+ "@gradio/atoms": "^0.9.0",
21
+ "@gradio/markdown": "^0.10.0",
22
+ "@gradio/button": "^0.3.0",
23
+ "@gradio/statustracker": "^0.8.0",
24
+ "@gradio/upload": "^0.13.0",
25
+ "@gradio/utils": "^0.7.0",
26
+ "@gradio/client": "^1.6.0"
27
27
  },
28
28
  "exports": {
29
29
  ".": {
@@ -42,7 +42,7 @@
42
42
  "svelte": "^4.0.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@gradio/preview": "^0.11.2-beta.0"
45
+ "@gradio/preview": "^0.12.0"
46
46
  },
47
47
  "repository": {
48
48
  "type": "git",
@@ -0,0 +1,10 @@
1
+ <script lang="ts">
2
+ export let transform: string;
3
+ </script>
4
+
5
+ <svg viewBox="0 0 24 24" width="16" height="16">
6
+ <path
7
+ d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"
8
+ {transform}
9
+ />
10
+ </svg>
@@ -0,0 +1,111 @@
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+ import Arrow from "./Arrow.svelte";
4
+ import type { I18nFormatter } from "js/utils/src";
5
+
6
+ export let x: number;
7
+ export let y: number;
8
+ export let on_add_row_above: () => void;
9
+ export let on_add_row_below: () => void;
10
+ export let on_add_column_left: () => void;
11
+ export let on_add_column_right: () => void;
12
+ export let row: number;
13
+
14
+ export let i18n: I18nFormatter;
15
+ let menu_element: HTMLDivElement;
16
+
17
+ $: is_header = row === -1;
18
+
19
+ onMount(() => {
20
+ position_menu();
21
+ });
22
+
23
+ function position_menu(): void {
24
+ if (!menu_element) return;
25
+
26
+ const viewport_width = window.innerWidth;
27
+ const viewport_height = window.innerHeight;
28
+ const menu_rect = menu_element.getBoundingClientRect();
29
+
30
+ let new_x = x - 30;
31
+ let new_y = y - 20;
32
+
33
+ if (new_x + menu_rect.width > viewport_width) {
34
+ new_x = x - menu_rect.width + 10;
35
+ }
36
+
37
+ if (new_y + menu_rect.height > viewport_height) {
38
+ new_y = y - menu_rect.height + 10;
39
+ }
40
+
41
+ menu_element.style.left = `${new_x}px`;
42
+ menu_element.style.top = `${new_y}px`;
43
+ }
44
+ </script>
45
+
46
+ <div bind:this={menu_element} class="cell-menu">
47
+ {#if !is_header}
48
+ <button on:click={() => on_add_row_above()}>
49
+ <Arrow transform="rotate(-90 12 12)" />
50
+ {i18n("dataframe.add_row_above")}
51
+ </button>
52
+ <button on:click={() => on_add_row_below()}>
53
+ <Arrow transform="rotate(90 12 12)" />
54
+ {i18n("dataframe.add_row_below")}
55
+ </button>
56
+ {/if}
57
+ <button on:click={() => on_add_column_left()}>
58
+ <Arrow transform="rotate(180 12 12)" />
59
+ {i18n("dataframe.add_column_left")}
60
+ </button>
61
+ <button on:click={() => on_add_column_right()}>
62
+ <Arrow transform="rotate(0 12 12)" />
63
+ {i18n("dataframe.add_column_right")}
64
+ </button>
65
+ </div>
66
+
67
+ <style>
68
+ .cell-menu {
69
+ position: fixed;
70
+ z-index: var(--layer-2);
71
+ background: var(--background-fill-primary);
72
+ border: 1px solid var(--border-color-primary);
73
+ border-radius: var(--radius-sm);
74
+ padding: var(--size-1);
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: var(--size-1);
78
+ box-shadow: var(--shadow-drop-lg);
79
+ min-width: 150px;
80
+ }
81
+
82
+ .cell-menu button {
83
+ background: none;
84
+ border: none;
85
+ cursor: pointer;
86
+ text-align: left;
87
+ padding: var(--size-1) var(--size-2);
88
+ border-radius: var(--radius-sm);
89
+ color: var(--body-text-color);
90
+ font-size: var(--text-sm);
91
+ transition:
92
+ background-color 0.2s,
93
+ color 0.2s;
94
+ display: flex;
95
+ align-items: center;
96
+ gap: var(--size-2);
97
+ }
98
+
99
+ .cell-menu button:hover {
100
+ background-color: var(--background-fill-secondary);
101
+ }
102
+
103
+ .cell-menu button :global(svg) {
104
+ fill: currentColor;
105
+ transition: fill 0.2s;
106
+ }
107
+
108
+ .cell-menu button:hover :global(svg) {
109
+ fill: var(--color-accent);
110
+ }
111
+ </style>