@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.
@@ -4,19 +4,14 @@
4
4
  import { dequal } from "dequal/lite";
5
5
  import { copy } from "@gradio/utils";
6
6
  import { Upload } from "@gradio/upload";
7
- import { BaseButton } from "@gradio/button";
7
+
8
8
  import EditableCell from "./EditableCell.svelte";
9
9
  import type { SelectData } from "@gradio/utils";
10
10
  import type { I18nFormatter } from "js/core/src/gradio_helper";
11
11
  import { type Client } from "@gradio/client";
12
12
  import VirtualTable from "./VirtualTable.svelte";
13
- import type {
14
- Headers,
15
- HeadersWithIDs,
16
- Data,
17
- Metadata,
18
- Datatype
19
- } from "./utils";
13
+ import type { Headers, HeadersWithIDs, Metadata, Datatype } from "./utils";
14
+ import CellMenu from "./CellMenu.svelte";
20
15
 
21
16
  export let datatype: Datatype | Datatype[];
22
17
  export let label: string | null = null;
@@ -64,7 +59,7 @@
64
59
  $: {
65
60
  if (selected !== false) {
66
61
  const [row, col] = selected;
67
- if (!isNaN(row) && !isNaN(col)) {
62
+ if (!isNaN(row) && !isNaN(col) && data[row]) {
68
63
  dispatch("select", {
69
64
  index: [row, col],
70
65
  value: get_data_at(row, col),
@@ -346,7 +341,14 @@
346
341
  }
347
342
  }
348
343
 
344
+ let active_cell: { row: number; col: number } | null = null;
345
+
349
346
  async function handle_cell_click(i: number, j: number): Promise<void> {
347
+ if (active_cell && active_cell.row === i && active_cell.col === j) {
348
+ active_cell = null;
349
+ } else {
350
+ active_cell = { row: i, col: j };
351
+ }
350
352
  if (dequal(editing, [i, j])) return;
351
353
  header_edit = false;
352
354
  selected_header = false;
@@ -410,35 +412,39 @@
410
412
  return;
411
413
  }
412
414
 
413
- data.splice(
414
- index ? index + 1 : data.length,
415
- 0,
416
- Array(data[0].length)
417
- .fill(0)
418
- .map((_, i) => {
419
- const _id = make_id();
415
+ const new_row = Array(data[0].length)
416
+ .fill(0)
417
+ .map((_, i) => {
418
+ const _id = make_id();
419
+ els[_id] = { cell: null, input: null };
420
+ return { id: _id, value: "" };
421
+ });
420
422
 
421
- els[_id] = { cell: null, input: null };
422
- return { id: _id, value: "" };
423
- })
424
- );
423
+ if (index !== undefined && index >= 0 && index <= data.length) {
424
+ data.splice(index, 0, new_row);
425
+ } else {
426
+ data.push(new_row);
427
+ }
425
428
 
426
429
  data = data;
427
- selected = [index ? index + 1 : data.length - 1, 0];
430
+ selected = [index !== undefined ? index : data.length - 1, 0];
428
431
  }
429
432
 
430
433
  $: (data || selected_header) && trigger_change();
431
434
 
432
- async function add_col(): Promise<void> {
435
+ async function add_col(index?: number): Promise<void> {
433
436
  parent.focus();
434
437
  if (col_count[1] !== "dynamic") return;
438
+
439
+ const insert_index = index !== undefined ? index : data[0].length;
440
+
435
441
  for (let i = 0; i < data.length; i++) {
436
442
  const _id = make_id();
437
443
  els[_id] = { cell: null, input: null };
438
- data[i].push({ id: _id, value: "" });
444
+ data[i].splice(insert_index, 0, { id: _id, value: "" });
439
445
  }
440
446
 
441
- headers.push(`Header ${headers.length + 1}`);
447
+ headers.splice(insert_index, 0, `Header ${headers.length + 1}`);
442
448
 
443
449
  data = data;
444
450
  headers = headers;
@@ -446,13 +452,23 @@
446
452
  await tick();
447
453
 
448
454
  requestAnimationFrame(() => {
449
- edit_header(headers.length - 1, true);
455
+ edit_header(insert_index, true);
450
456
  const new_w = parent.querySelectorAll("tbody")[1].offsetWidth;
451
457
  parent.querySelectorAll("table")[1].scrollTo({ left: new_w });
452
458
  });
453
459
  }
454
460
 
455
461
  function handle_click_outside(event: Event): void {
462
+ if (
463
+ (active_cell_menu &&
464
+ !(event.target as HTMLElement).closest(".cell-menu")) ||
465
+ (active_header_menu &&
466
+ !(event.target as HTMLElement).closest(".cell-menu"))
467
+ ) {
468
+ active_cell_menu = null;
469
+ active_header_menu = null;
470
+ }
471
+
456
472
  event.stopImmediatePropagation();
457
473
  const [trigger] = event.composedPath() as HTMLElement[];
458
474
  if (parent.contains(trigger)) {
@@ -463,6 +479,9 @@
463
479
  header_edit = false;
464
480
  selected_header = false;
465
481
  selected = false;
482
+ active_cell = null;
483
+ active_cell_menu = null;
484
+ active_header_menu = null;
466
485
  }
467
486
 
468
487
  function guess_delimitaor(
@@ -640,6 +659,107 @@
640
659
  observer.disconnect();
641
660
  };
642
661
  });
662
+
663
+ let highlighted_column: number | null = null;
664
+
665
+ let active_cell_menu: {
666
+ row: number;
667
+ col: number;
668
+ x: number;
669
+ y: number;
670
+ } | null = null;
671
+
672
+ function toggle_cell_menu(event: MouseEvent, row: number, col: number): void {
673
+ event.stopPropagation();
674
+ if (
675
+ active_cell_menu &&
676
+ active_cell_menu.row === row &&
677
+ active_cell_menu.col === col
678
+ ) {
679
+ active_cell_menu = null;
680
+ } else {
681
+ const cell = (event.target as HTMLElement).closest("td");
682
+ if (cell) {
683
+ const rect = cell.getBoundingClientRect();
684
+ active_cell_menu = {
685
+ row,
686
+ col,
687
+ x: rect.right,
688
+ y: rect.bottom
689
+ };
690
+ }
691
+ }
692
+ }
693
+
694
+ function add_row_at(index: number, position: "above" | "below"): void {
695
+ const row_index = position === "above" ? index : index + 1;
696
+ add_row(row_index);
697
+ active_cell_menu = null;
698
+ active_header_menu = null;
699
+ }
700
+
701
+ function add_col_at(index: number, position: "left" | "right"): void {
702
+ const col_index = position === "left" ? index : index + 1;
703
+ add_col(col_index);
704
+ active_cell_menu = null;
705
+ active_header_menu = null;
706
+ }
707
+
708
+ onMount(() => {
709
+ document.addEventListener("click", handle_click_outside);
710
+ return () => {
711
+ document.removeEventListener("click", handle_click_outside);
712
+ };
713
+ });
714
+
715
+ let active_button: {
716
+ type: "header" | "cell";
717
+ row?: number;
718
+ col: number;
719
+ } | null = null;
720
+
721
+ function toggle_header_button(col: number): void {
722
+ if (active_button?.type === "header" && active_button.col === col) {
723
+ active_button = null;
724
+ } else {
725
+ active_button = { type: "header", col };
726
+ }
727
+ }
728
+
729
+ function toggle_cell_button(row: number, col: number): void {
730
+ if (
731
+ active_button?.type === "cell" &&
732
+ active_button.row === row &&
733
+ active_button.col === col
734
+ ) {
735
+ active_button = null;
736
+ } else {
737
+ active_button = { type: "cell", row, col };
738
+ }
739
+ }
740
+
741
+ let active_header_menu: {
742
+ col: number;
743
+ x: number;
744
+ y: number;
745
+ } | null = null;
746
+
747
+ function toggle_header_menu(event: MouseEvent, col: number): void {
748
+ event.stopPropagation();
749
+ if (active_header_menu && active_header_menu.col === col) {
750
+ active_header_menu = null;
751
+ } else {
752
+ const header = (event.target as HTMLElement).closest("th");
753
+ if (header) {
754
+ const rect = header.getBoundingClientRect();
755
+ active_header_menu = {
756
+ col,
757
+ x: rect.right,
758
+ y: rect.bottom
759
+ };
760
+ }
761
+ }
762
+ }
643
763
  </script>
644
764
 
645
765
  <svelte:window
@@ -758,40 +878,58 @@
758
878
  class:focus={header_edit === i || selected_header === i}
759
879
  aria-sort={get_sort_status(value, sort_by, sort_direction)}
760
880
  style="width: var(--cell-width-{i});"
881
+ on:click={() => {
882
+ toggle_header_button(i);
883
+ }}
761
884
  >
762
885
  <div class="cell-wrap">
763
- <EditableCell
764
- bind:value={_headers[i].value}
765
- bind:el={els[id].input}
766
- {latex_delimiters}
767
- {line_breaks}
768
- edit={header_edit === i}
769
- on:keydown={end_header_edit}
770
- on:dblclick={() => edit_header(i)}
771
- {select_on_focus}
772
- header
773
- {root}
774
- />
775
-
776
- <!-- TODO: fix -->
777
- <!-- svelte-ignore a11y-click-events-have-key-events -->
778
- <!-- svelte-ignore a11y-no-static-element-interactions-->
779
- <div
780
- class:sorted={sort_by === i}
781
- class:des={sort_by === i && sort_direction === "des"}
782
- class="sort-button {sort_direction} "
783
- on:click={() => handle_sort(i)}
784
- >
785
- <svg
786
- width="1em"
787
- height="1em"
788
- viewBox="0 0 9 7"
789
- fill="none"
790
- xmlns="http://www.w3.org/2000/svg"
886
+ <div class="header-content">
887
+ <EditableCell
888
+ bind:value={_headers[i].value}
889
+ bind:el={els[id].input}
890
+ {latex_delimiters}
891
+ {line_breaks}
892
+ edit={header_edit === i}
893
+ on:keydown={end_header_edit}
894
+ on:dblclick={() => edit_header(i)}
895
+ {select_on_focus}
896
+ header
897
+ {root}
898
+ />
899
+ <!-- TODO: fix -->
900
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
901
+ <!-- svelte-ignore a11y-no-static-element-interactions-->
902
+ <div
903
+ class:sorted={sort_by === i}
904
+ class:des={sort_by === i && sort_direction === "des"}
905
+ class="sort-button {sort_direction}"
906
+ on:click={(event) => {
907
+ event.stopPropagation();
908
+ handle_sort(i);
909
+ }}
791
910
  >
792
- <path d="M4.49999 0L8.3971 6.75H0.602875L4.49999 0Z" />
793
- </svg>
911
+ <svg
912
+ width="1em"
913
+ height="1em"
914
+ viewBox="0 0 9 7"
915
+ fill="none"
916
+ xmlns="http://www.w3.org/2000/svg"
917
+ >
918
+ <path d="M4.49999 0L8.3971 6.75H0.602875L4.49999 0Z" />
919
+ </svg>
920
+ </div>
794
921
  </div>
922
+
923
+ {#if editable}
924
+ <button
925
+ class="cell-menu-button"
926
+ class:visible={active_button?.type === "header" &&
927
+ active_button.col === i}
928
+ on:click={(event) => toggle_header_menu(event, i)}
929
+ >
930
+
931
+ </button>
932
+ {/if}
795
933
  </div>
796
934
  </th>
797
935
  {/each}
@@ -802,11 +940,17 @@
802
940
  <td
803
941
  tabindex="0"
804
942
  on:touchstart={() => start_edit(index, j)}
805
- on:click={() => handle_cell_click(index, j)}
943
+ on:click={() => {
944
+ handle_cell_click(index, j);
945
+ toggle_cell_button(index, j);
946
+ }}
806
947
  on:dblclick={() => start_edit(index, j)}
807
948
  style:width="var(--cell-width-{j})"
808
949
  style={styling?.[index]?.[j] || ""}
809
950
  class:focus={dequal(selected, [index, j])}
951
+ class:menu-active={active_cell_menu &&
952
+ active_cell_menu.row === index &&
953
+ active_cell_menu.col === j}
810
954
  >
811
955
  <div class="cell-wrap">
812
956
  <EditableCell
@@ -822,6 +966,17 @@
822
966
  {clear_on_focus}
823
967
  {root}
824
968
  />
969
+ {#if editable}
970
+ <button
971
+ class="cell-menu-button"
972
+ class:visible={active_button?.type === "cell" &&
973
+ active_button.row === index &&
974
+ active_button.col === j}
975
+ on:click={(event) => toggle_cell_menu(event, index, j)}
976
+ >
977
+
978
+ </button>
979
+ {/if}
825
980
  </div>
826
981
  </td>
827
982
  {/each}
@@ -829,64 +984,35 @@
829
984
  </VirtualTable>
830
985
  </Upload>
831
986
  </div>
832
- {#if editable}
833
- <div class="controls-wrap">
834
- {#if row_count[1] === "dynamic"}
835
- <span class="button-wrap">
836
- <BaseButton
837
- variant="secondary"
838
- size="sm"
839
- on:click={(e) => (e.stopPropagation(), add_row())}
840
- >
841
- <svg
842
- xmlns="http://www.w3.org/2000/svg"
843
- xmlns:xlink="http://www.w3.org/1999/xlink"
844
- aria-hidden="true"
845
- role="img"
846
- width="1em"
847
- height="1em"
848
- preserveAspectRatio="xMidYMid meet"
849
- viewBox="0 0 32 32"
850
- >
851
- <path
852
- fill="currentColor"
853
- d="M24.59 16.59L17 24.17V4h-2v20.17l-7.59-7.58L6 18l10 10l10-10l-1.41-1.41z"
854
- />
855
- </svg>
856
- {i18n("dataframe.new_row")}
857
- </BaseButton>
858
- </span>
859
- {/if}
860
- {#if col_count[1] === "dynamic"}
861
- <span class="button-wrap">
862
- <BaseButton
863
- variant="secondary"
864
- size="sm"
865
- on:click={(e) => (e.stopPropagation(), add_col())}
866
- >
867
- <svg
868
- xmlns="http://www.w3.org/2000/svg"
869
- xmlns:xlink="http://www.w3.org/1999/xlink"
870
- aria-hidden="true"
871
- role="img"
872
- width="1em"
873
- height="1em"
874
- preserveAspectRatio="xMidYMid meet"
875
- viewBox="0 0 32 32"
876
- >
877
- <path
878
- fill="currentColor"
879
- d="m18 6l-1.43 1.393L24.15 15H4v2h20.15l-7.58 7.573L18 26l10-10L18 6z"
880
- />
881
- </svg>
882
- {i18n("dataframe.new_column")}
883
- </BaseButton>
884
- </span>
885
- {/if}
886
- </div>
887
- {/if}
888
987
  </div>
889
988
 
989
+ {#if active_cell_menu !== null}
990
+ <CellMenu
991
+ {i18n}
992
+ x={active_cell_menu.x}
993
+ y={active_cell_menu.y}
994
+ col={active_cell_menu.col}
995
+ row={active_cell_menu?.row ?? -1}
996
+ on_add_row_above={() => add_row_at(active_cell_menu?.row ?? -1, "above")}
997
+ on_add_row_below={() => add_row_at(active_cell_menu?.row ?? -1, "below")}
998
+ on_add_column_left={() => add_col_at(active_cell_menu?.col ?? -1, "left")}
999
+ on_add_column_right={() => add_col_at(active_cell_menu?.col ?? -1, "right")}
1000
+ />
1001
+ {/if}
1002
+
1003
+ {#if active_header_menu !== null}
1004
+ <CellMenu
1005
+ {i18n}
1006
+ x={active_header_menu.x}
1007
+ y={active_header_menu.y}
1008
+ col={active_header_menu.col}
1009
+ row={-1}
1010
+ on_add_column_left={() => add_col_at(active_header_menu?.col ?? -1, "left")}
1011
+ on_add_column_right={() =>
1012
+ add_col_at(active_header_menu?.col ?? -1, "right")}
1013
+ />
1014
+ {/if}
1015
+
890
1016
  <style>
891
1017
  .button-wrap:hover svg {
892
1018
  color: var(--color-accent);
@@ -987,8 +1113,9 @@
987
1113
  }
988
1114
 
989
1115
  th.focus,
990
- td.focus {
991
- --ring-color: var(--color-accent);
1116
+ td.focus,
1117
+ td.menu-active {
1118
+ z-index: 1;
992
1119
  }
993
1120
 
994
1121
  tr:last-child td:first-child {
@@ -1037,21 +1164,22 @@
1037
1164
  }
1038
1165
 
1039
1166
  .cell-wrap {
1167
+ position: relative;
1040
1168
  display: flex;
1041
1169
  align-items: center;
1170
+ justify-content: space-between;
1042
1171
  outline: none;
1043
1172
  height: var(--size-full);
1044
1173
  min-height: var(--size-9);
1174
+ overflow: hidden;
1045
1175
  }
1046
1176
 
1047
- .controls-wrap {
1177
+ .header-content {
1048
1178
  display: flex;
1049
- justify-content: flex-end;
1050
- padding-top: var(--size-2);
1051
- }
1052
-
1053
- .controls-wrap > * + * {
1054
- margin-left: var(--size-1);
1179
+ align-items: center;
1180
+ overflow: hidden;
1181
+ flex-grow: 1;
1182
+ min-width: 0;
1055
1183
  }
1056
1184
 
1057
1185
  .row_odd {
@@ -1065,4 +1193,45 @@
1065
1193
  table {
1066
1194
  border-collapse: separate;
1067
1195
  }
1196
+
1197
+ .select-column {
1198
+ width: var(--size-3);
1199
+ text-align: center;
1200
+ padding: var(--size-1);
1201
+ border-right: none;
1202
+ }
1203
+
1204
+ .cell-menu-button {
1205
+ flex-shrink: 0;
1206
+ display: none;
1207
+ background-color: var(--block-background-fill);
1208
+ border: 1px solid var(--border-color-primary);
1209
+ border-radius: var(--block-radius);
1210
+ width: var(--size-5);
1211
+ height: var(--size-5);
1212
+ min-width: var(--size-5);
1213
+ padding: 0;
1214
+ margin-right: var(--spacing-sm);
1215
+ z-index: var(--layer-2);
1216
+ }
1217
+
1218
+ .cell-menu-button:hover {
1219
+ background-color: var(--color-bg-hover);
1220
+ }
1221
+
1222
+ .cell-menu-button.visible {
1223
+ display: flex;
1224
+ align-items: center;
1225
+ justify-content: center;
1226
+ }
1227
+
1228
+ th .cell-wrap {
1229
+ padding-right: var(--spacing-sm);
1230
+ }
1231
+
1232
+ th .header-content {
1233
+ white-space: nowrap;
1234
+ overflow: hidden;
1235
+ text-overflow: ellipsis;
1236
+ }
1068
1237
  </style>