@gradio/dataframe 0.12.6 → 0.13.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.
@@ -1,12 +1,12 @@
1
- <script>import { createEventDispatcher, tick, onMount } from "svelte";
1
+ <script>import { afterUpdate, createEventDispatcher, tick, onMount } from "svelte";
2
2
  import { dsvFormat } from "d3-dsv";
3
3
  import { dequal } from "dequal/lite";
4
- import { copy } from "@gradio/utils";
5
4
  import { Upload } from "@gradio/upload";
6
5
  import EditableCell from "./EditableCell.svelte";
7
6
  import {} from "@gradio/client";
8
7
  import VirtualTable from "./VirtualTable.svelte";
9
8
  import CellMenu from "./CellMenu.svelte";
9
+ import Toolbar from "./Toolbar.svelte";
10
10
  export let datatype;
11
11
  export let label = null;
12
12
  export let show_label = true;
@@ -22,9 +22,13 @@ export let i18n;
22
22
  export let max_height = 500;
23
23
  export let line_breaks = true;
24
24
  export let column_widths = [];
25
+ export let show_row_numbers = false;
25
26
  export let upload;
26
27
  export let stream_handler;
28
+ export let show_fullscreen_button = false;
29
+ export let value_is_output = false;
27
30
  let selected = false;
31
+ let clicked_cell = void 0;
28
32
  export let display_value = null;
29
33
  export let styling = null;
30
34
  let t_rect;
@@ -86,30 +90,39 @@ function process_data(_values) {
86
90
  );
87
91
  }
88
92
  let _headers = make_headers(headers);
89
- let old_headers;
93
+ let old_headers = headers;
90
94
  $: {
91
95
  if (!dequal(headers, old_headers)) {
92
- trigger_headers();
96
+ _headers = make_headers(headers);
97
+ old_headers = JSON.parse(JSON.stringify(headers));
93
98
  }
94
99
  }
95
- function trigger_headers() {
96
- _headers = make_headers(headers);
97
- old_headers = headers.slice();
98
- trigger_change();
99
- }
100
+ let data = [[]];
101
+ let old_val = void 0;
100
102
  $:
101
103
  if (!dequal(values, old_val)) {
102
104
  data = process_data(values);
103
- old_val = values;
105
+ old_val = JSON.parse(JSON.stringify(values));
104
106
  }
105
- let data = [[]];
106
- let old_val = void 0;
107
+ let previous_headers = _headers.map((h) => h.value);
108
+ let previous_data = data.map((row) => row.map((cell) => String(cell.value)));
107
109
  async function trigger_change() {
108
- dispatch("change", {
109
- data: data.map((r) => r.map(({ value }) => value)),
110
- headers: _headers.map((h) => h.value),
111
- metadata: editable ? null : { display_value, styling }
112
- });
110
+ const current_headers = _headers.map((h) => h.value);
111
+ const current_data = data.map(
112
+ (row) => row.map((cell) => String(cell.value))
113
+ );
114
+ if (!dequal(current_data, previous_data) || !dequal(current_headers, previous_headers)) {
115
+ dispatch("change", {
116
+ data: data.map((row) => row.map((cell) => cell.value)),
117
+ headers: _headers.map((h) => h.value),
118
+ metadata: null
119
+ });
120
+ if (!value_is_output) {
121
+ dispatch("input");
122
+ }
123
+ previous_data = current_data;
124
+ previous_headers = current_headers;
125
+ }
113
126
  }
114
127
  function get_sort_status(name, _sort, direction) {
115
128
  if (!_sort)
@@ -134,11 +147,6 @@ function get_current_indices(id) {
134
147
  [-1, -1]
135
148
  );
136
149
  }
137
- async function start_edit(i, j) {
138
- if (!editable || dequal(editing, [i, j]))
139
- return;
140
- editing = [i, j];
141
- }
142
150
  function move_cursor(key, current_coords) {
143
151
  const dir = {
144
152
  ArrowRight: [0, 1],
@@ -257,24 +265,6 @@ async function handle_keydown(event) {
257
265
  }
258
266
  }
259
267
  }
260
- let active_cell = null;
261
- async function handle_cell_click(i, j) {
262
- if (active_cell && active_cell.row === i && active_cell.col === j) {
263
- active_cell = null;
264
- } else {
265
- active_cell = { row: i, col: j };
266
- }
267
- if (dequal(editing, [i, j]))
268
- return;
269
- header_edit = false;
270
- selected_header = false;
271
- editing = false;
272
- if (!dequal(selected, [i, j])) {
273
- selected = [i, j];
274
- await tick();
275
- parent.focus();
276
- }
277
- }
278
268
  let sort_direction;
279
269
  let sort_by;
280
270
  function handle_sort(col) {
@@ -337,7 +327,7 @@ async function add_row(index) {
337
327
  selected = [index !== void 0 ? index : data.length - 1, 0];
338
328
  }
339
329
  $:
340
- (data || selected_header) && trigger_change();
330
+ (data || _headers) && trigger_change();
341
331
  async function add_col(index) {
342
332
  parent.focus();
343
333
  if (col_count[1] !== "dynamic")
@@ -363,16 +353,15 @@ function handle_click_outside(event) {
363
353
  active_cell_menu = null;
364
354
  active_header_menu = null;
365
355
  }
366
- event.stopImmediatePropagation();
367
356
  const [trigger] = event.composedPath();
368
357
  if (parent.contains(trigger)) {
369
358
  return;
370
359
  }
360
+ clicked_cell = void 0;
371
361
  editing = false;
362
+ selected = false;
372
363
  header_edit = false;
373
364
  selected_header = false;
374
- reset_selection();
375
- active_cell = null;
376
365
  active_cell_menu = null;
377
366
  active_header_menu = null;
378
367
  }
@@ -421,6 +410,8 @@ function blob_to_string(blob) {
421
410
  }
422
411
  let dragging = false;
423
412
  function get_max(_d) {
413
+ if (!_d || _d.length === 0 || !_d[0])
414
+ return [];
424
415
  let max2 = _d[0].slice();
425
416
  for (let i = 0; i < _d.length; i++) {
426
417
  for (let j = 0; j < _d[i].length; j++) {
@@ -505,8 +496,17 @@ onMount(() => {
505
496
  });
506
497
  });
507
498
  observer.observe(parent);
499
+ document.addEventListener("click", handle_click_outside);
500
+ window.addEventListener("resize", handle_resize);
501
+ document.addEventListener("fullscreenchange", handle_fullscreen_change);
508
502
  return () => {
509
503
  observer.disconnect();
504
+ document.removeEventListener("click", handle_click_outside);
505
+ window.removeEventListener("resize", handle_resize);
506
+ document.removeEventListener(
507
+ "fullscreenchange",
508
+ handle_fullscreen_change
509
+ );
510
510
  };
511
511
  });
512
512
  let highlighted_column = null;
@@ -545,14 +545,6 @@ function handle_resize() {
545
545
  active_header_menu = null;
546
546
  set_cell_widths();
547
547
  }
548
- onMount(() => {
549
- document.addEventListener("click", handle_click_outside);
550
- window.addEventListener("resize", handle_resize);
551
- return () => {
552
- document.removeEventListener("click", handle_click_outside);
553
- window.removeEventListener("resize", handle_resize);
554
- };
555
- });
556
548
  let active_button = null;
557
549
  function toggle_header_button(col) {
558
550
  if (active_button?.type === "header" && active_button.col === col) {
@@ -569,6 +561,19 @@ function toggle_cell_button(row, col) {
569
561
  }
570
562
  }
571
563
  let active_header_menu = null;
564
+ let is_fullscreen = false;
565
+ function toggle_fullscreen() {
566
+ if (!document.fullscreenElement) {
567
+ parent.requestFullscreen();
568
+ is_fullscreen = true;
569
+ } else {
570
+ document.exitFullscreen();
571
+ is_fullscreen = false;
572
+ }
573
+ }
574
+ function handle_fullscreen_change() {
575
+ is_fullscreen = !!document.fullscreenElement;
576
+ }
572
577
  function toggle_header_menu(event, col) {
573
578
  event.stopPropagation();
574
579
  if (active_header_menu && active_header_menu.col === col) {
@@ -585,24 +590,26 @@ function toggle_header_menu(event, col) {
585
590
  }
586
591
  }
587
592
  }
588
- function reset_selection() {
589
- selected = false;
590
- last_selected = null;
591
- }
593
+ afterUpdate(() => {
594
+ value_is_output = false;
595
+ });
592
596
  </script>
593
597
 
594
- <svelte:window
595
- on:click={handle_click_outside}
596
- on:touchstart={handle_click_outside}
597
- on:resize={() => set_cell_widths()}
598
- />
598
+ <svelte:window on:resize={() => set_cell_widths()} />
599
599
 
600
- <div class:label={label && label.length !== 0} use:copy>
601
- {#if label && label.length !== 0 && show_label}
602
- <p>
603
- {label}
604
- </p>
605
- {/if}
600
+ <div class="table-container">
601
+ <div class="header-row">
602
+ {#if label && label.length !== 0 && show_label}
603
+ <div class="label">
604
+ <p>{label}</p>
605
+ </div>
606
+ {/if}
607
+ <Toolbar
608
+ {show_fullscreen_button}
609
+ {is_fullscreen}
610
+ on:click={toggle_fullscreen}
611
+ />
612
+ </div>
606
613
  <div
607
614
  bind:this={parent}
608
615
  class="table-wrap"
@@ -623,6 +630,9 @@ function reset_selection() {
623
630
  {/if}
624
631
  <thead>
625
632
  <tr>
633
+ {#if show_row_numbers}
634
+ <th class="row-number-header"></th>
635
+ {/if}
626
636
  {#each _headers as { value, id }, i (id)}
627
637
  <th
628
638
  class:editing={header_edit === i}
@@ -702,6 +712,9 @@ function reset_selection() {
702
712
  <caption class="sr-only">{label}</caption>
703
713
  {/if}
704
714
  <tr slot="thead">
715
+ {#if show_row_numbers}
716
+ <th class="row-number-header"></th>
717
+ {/if}
705
718
  {#each _headers as { value, id }, i (id)}
706
719
  <th
707
720
  class:focus={header_edit === i || selected_header === i}
@@ -721,17 +734,14 @@ function reset_selection() {
721
734
  edit={header_edit === i}
722
735
  on:keydown={end_header_edit}
723
736
  on:dblclick={() => edit_header(i)}
724
- {select_on_focus}
725
737
  header
726
738
  {root}
727
739
  />
728
- <!-- TODO: fix -->
729
- <!-- svelte-ignore a11y-click-events-have-key-events -->
730
- <!-- svelte-ignore a11y-no-static-element-interactions-->
731
- <div
740
+ <button
732
741
  class:sorted={sort_by === i}
733
742
  class:des={sort_by === i && sort_direction === "des"}
734
743
  class="sort-button {sort_direction}"
744
+ tabindex="0"
735
745
  on:click={(event) => {
736
746
  event.stopPropagation();
737
747
  handle_sort(i);
@@ -746,7 +756,7 @@ function reset_selection() {
746
756
  >
747
757
  <path d="M4.49999 0L8.3971 6.75H0.602875L4.49999 0Z" />
748
758
  </svg>
749
- </div>
759
+ </button>
750
760
  </div>
751
761
 
752
762
  {#if editable}
@@ -763,15 +773,44 @@ function reset_selection() {
763
773
  </tr>
764
774
 
765
775
  <tr slot="tbody" let:item let:index class:row_odd={index % 2 === 0}>
776
+ {#if show_row_numbers}
777
+ <td class="row-number" title={`Row ${index + 1}`}>{index + 1}</td>
778
+ {/if}
766
779
  {#each item as { value, id }, j (id)}
767
780
  <td
768
781
  tabindex="0"
769
- on:touchstart={() => start_edit(index, j)}
770
- on:click={() => {
771
- handle_cell_click(index, j);
782
+ on:touchstart={(event) => {
783
+ event.preventDefault();
784
+ event.stopPropagation();
785
+ clear_on_focus = false;
786
+ clicked_cell = { row: index, col: j };
787
+ selected = [index, j];
788
+ selected_header = false;
789
+ header_edit = false;
790
+ if (editable) {
791
+ editing = [index, j];
792
+ }
793
+ toggle_cell_button(index, j);
794
+ }}
795
+ on:mousedown={(event) => {
796
+ event.preventDefault();
797
+ event.stopPropagation();
798
+ }}
799
+ on:click={(event) => {
800
+ event.preventDefault();
801
+ event.stopPropagation();
802
+ clear_on_focus = false;
803
+ active_cell_menu = null;
804
+ active_header_menu = null;
805
+ clicked_cell = { row: index, col: j };
806
+ selected = [index, j];
807
+ selected_header = false;
808
+ header_edit = false;
809
+ if (editable) {
810
+ editing = [index, j];
811
+ }
772
812
  toggle_cell_button(index, j);
773
813
  }}
774
- on:dblclick={() => start_edit(index, j)}
775
814
  style:width="var(--cell-width-{j})"
776
815
  style={styling?.[index]?.[j] || ""}
777
816
  class:focus={dequal(selected, [index, j])}
@@ -789,7 +828,10 @@ function reset_selection() {
789
828
  {editable}
790
829
  edit={dequal(editing, [index, j])}
791
830
  datatype={Array.isArray(datatype) ? datatype[j] : datatype}
792
- on:blur={() => ((clear_on_focus = false), parent.focus())}
831
+ on:blur={() => {
832
+ clear_on_focus = false;
833
+ parent.focus();
834
+ }}
793
835
  {clear_on_focus}
794
836
  {root}
795
837
  />
@@ -1056,4 +1098,60 @@ function reset_selection() {
1056
1098
  overflow-wrap: break-word;
1057
1099
  word-break: break-word;
1058
1100
  }
1101
+
1102
+ .table-container {
1103
+ display: flex;
1104
+ flex-direction: column;
1105
+ gap: var(--size-2);
1106
+ }
1107
+
1108
+ .row-number,
1109
+ .row-number-header {
1110
+ width: var(--size-7);
1111
+ min-width: var(--size-7);
1112
+ text-align: center;
1113
+ background: var(--table-even-background-fill);
1114
+ position: sticky;
1115
+ left: 0;
1116
+ font-size: var(--input-text-size);
1117
+ color: var(--body-text-color);
1118
+ padding: var(--size-1) var(--size-2);
1119
+ overflow: hidden;
1120
+ text-overflow: ellipsis;
1121
+ white-space: nowrap;
1122
+ font-weight: var(--weight-semibold);
1123
+ }
1124
+
1125
+ .row-number-header {
1126
+ z-index: var(--layer-2);
1127
+ }
1128
+
1129
+ .row-number {
1130
+ z-index: var(--layer-1);
1131
+ }
1132
+
1133
+ :global(tbody > tr:nth-child(odd)) .row-number {
1134
+ background: var(--table-odd-background-fill);
1135
+ }
1136
+
1137
+ .header-row {
1138
+ display: flex;
1139
+ justify-content: space-between;
1140
+ align-items: center;
1141
+ gap: var(--size-2);
1142
+ height: var(--size-6);
1143
+ min-height: var(--size-6);
1144
+ }
1145
+
1146
+ .label {
1147
+ flex: 1;
1148
+ }
1149
+
1150
+ .label p {
1151
+ position: relative;
1152
+ z-index: var(--layer-4);
1153
+ margin: 0;
1154
+ color: var(--block-label-text-color);
1155
+ font-size: var(--block-label-text-size);
1156
+ }
1059
1157
  </style>
@@ -2,7 +2,7 @@ import { SvelteComponent } from "svelte";
2
2
  import type { SelectData } from "@gradio/utils";
3
3
  import type { I18nFormatter } from "js/core/src/gradio_helper";
4
4
  import { type Client } from "@gradio/client";
5
- import type { Headers, Metadata, Datatype } from "./utils";
5
+ import type { Headers, DataframeValue, Datatype } from "./utils";
6
6
  declare const __propDef: {
7
7
  props: {
8
8
  datatype: Datatype | Datatype[];
@@ -24,17 +24,17 @@ declare const __propDef: {
24
24
  max_height?: number | undefined;
25
25
  line_breaks?: boolean | undefined;
26
26
  column_widths?: string[] | undefined;
27
+ show_row_numbers?: boolean | undefined;
27
28
  upload: Client["upload"];
28
29
  stream_handler: Client["stream"];
30
+ show_fullscreen_button?: boolean | undefined;
31
+ value_is_output?: boolean | undefined;
29
32
  display_value?: (string[][] | null) | undefined;
30
33
  styling?: (string[][] | null) | undefined;
31
34
  };
32
35
  events: {
33
- change: CustomEvent<{
34
- data: (string | number)[][];
35
- headers: string[];
36
- metadata: Metadata;
37
- }>;
36
+ change: CustomEvent<DataframeValue>;
37
+ input: CustomEvent<undefined>;
38
38
  select: CustomEvent<SelectData>;
39
39
  } & {
40
40
  [evt: string]: CustomEvent<any>;
@@ -0,0 +1,49 @@
1
+ <script>import { Maximize, Minimize } from "@gradio/icons";
2
+ export let show_fullscreen_button = false;
3
+ export let is_fullscreen = false;
4
+ </script>
5
+
6
+ {#if show_fullscreen_button}
7
+ <div class="toolbar">
8
+ <button class="toolbar-button" on:click>
9
+ {#if is_fullscreen}
10
+ <Minimize />
11
+ {:else}
12
+ <Maximize />
13
+ {/if}
14
+ </button>
15
+ </div>
16
+ {/if}
17
+
18
+ <style>
19
+ .toolbar {
20
+ display: flex;
21
+ justify-content: flex-end;
22
+ gap: var(--size-1);
23
+ }
24
+
25
+ .toolbar-button {
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ width: var(--size-6);
30
+ height: var(--size-6);
31
+ padding: var(--size-1);
32
+ border: none;
33
+ border-radius: var(--radius-sm);
34
+ background: transparent;
35
+ color: var(--body-text-color-subdued);
36
+ cursor: pointer;
37
+ transition: all 0.2s;
38
+ }
39
+
40
+ .toolbar-button:hover {
41
+ background: var(--background-fill-secondary);
42
+ color: var(--body-text-color);
43
+ }
44
+
45
+ .toolbar-button :global(svg) {
46
+ width: var(--size-4);
47
+ height: var(--size-4);
48
+ }
49
+ </style>
@@ -0,0 +1,19 @@
1
+ import { SvelteComponent } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ show_fullscreen_button?: boolean | undefined;
5
+ is_fullscreen?: boolean | undefined;
6
+ };
7
+ events: {
8
+ click: MouseEvent;
9
+ } & {
10
+ [evt: string]: CustomEvent<any>;
11
+ };
12
+ slots: {};
13
+ };
14
+ export type ToolbarProps = typeof __propDef.props;
15
+ export type ToolbarEvents = typeof __propDef.events;
16
+ export type ToolbarSlots = typeof __propDef.slots;
17
+ export default class Toolbar extends SvelteComponent<ToolbarProps, ToolbarEvents, ToolbarSlots> {
18
+ }
19
+ export {};
@@ -8,3 +8,8 @@ export type HeadersWithIDs = {
8
8
  value: string;
9
9
  id: string;
10
10
  }[];
11
+ export type DataframeValue = {
12
+ data: Data;
13
+ headers: Headers;
14
+ metadata: Metadata;
15
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradio/dataframe",
3
- "version": "0.12.6",
3
+ "version": "0.13.0",
4
4
  "description": "Gradio UI packages",
5
5
  "type": "module",
6
6
  "author": "",
@@ -17,13 +17,14 @@
17
17
  "dompurify": "^3.0.3",
18
18
  "katex": "^0.16.7",
19
19
  "marked": "^12.0.0",
20
- "@gradio/atoms": "^0.13.0",
21
- "@gradio/button": "^0.4.0",
20
+ "@gradio/atoms": "^0.13.1",
21
+ "@gradio/client": "^1.10.0",
22
+ "@gradio/button": "^0.4.2",
23
+ "@gradio/icons": "^0.10.0",
22
24
  "@gradio/markdown-code": "^0.3.0",
23
- "@gradio/client": "^1.9.0",
24
- "@gradio/upload": "^0.14.4",
25
- "@gradio/statustracker": "^0.10.0",
26
- "@gradio/utils": "^0.10.0"
25
+ "@gradio/statustracker": "^0.10.2",
26
+ "@gradio/utils": "^0.10.0",
27
+ "@gradio/upload": "^0.14.6"
27
28
  },
28
29
  "exports": {
29
30
  ".": {
@@ -20,7 +20,6 @@
20
20
  display: boolean;
21
21
  }[];
22
22
  export let clear_on_focus = false;
23
- export let select_on_focus = false;
24
23
  export let line_breaks = true;
25
24
  export let editable = true;
26
25
  export let root: string;
@@ -34,11 +33,10 @@
34
33
  if (clear_on_focus) {
35
34
  _value = "";
36
35
  }
37
- if (select_on_focus) {
38
- node.select();
39
- }
40
36
 
41
- node.focus();
37
+ requestAnimationFrame(() => {
38
+ node.focus();
39
+ });
42
40
 
43
41
  return {};
44
42
  }
@@ -69,6 +67,9 @@
69
67
  class:header
70
68
  tabindex="-1"
71
69
  on:blur={handle_blur}
70
+ on:mousedown|stopPropagation
71
+ on:mouseup|stopPropagation
72
+ on:click|stopPropagation
72
73
  use:use_focus
73
74
  on:keydown={handle_keydown}
74
75
  />
@@ -81,6 +82,8 @@
81
82
  class:edit
82
83
  on:focus|preventDefault
83
84
  style={styling}
85
+ class="table-cell-text"
86
+ placeholder=" "
84
87
  >
85
88
  {#if datatype === "html"}
86
89
  {@html value}
@@ -109,6 +112,7 @@
109
112
  outline: none;
110
113
  border: none;
111
114
  background: transparent;
115
+ cursor: text;
112
116
  }
113
117
 
114
118
  span {
@@ -119,11 +123,12 @@
119
123
  -moz-user-select: text;
120
124
  -ms-user-select: text;
121
125
  user-select: text;
126
+ cursor: text;
122
127
  }
123
128
 
124
129
  .header {
125
130
  transform: translateX(0);
126
- font: var(--weight-bold);
131
+ font-weight: var(--weight-bold);
127
132
  }
128
133
 
129
134
  .edit {