@gradio/dataframe 0.13.1 → 0.15.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @gradio/dataframe
2
2
 
3
+ ## 0.15.0
4
+
5
+ ### Features
6
+
7
+ - [#10456](https://github.com/gradio-app/gradio/pull/10456) [`8e40c15`](https://github.com/gradio-app/gradio/commit/8e40c15669ed1244d6f2288e55c2223279bd37a4) - Implement multiple cell selection. Thanks @hannahblair!
8
+ - [#10463](https://github.com/gradio-app/gradio/pull/10463) [`ed7a091`](https://github.com/gradio-app/gradio/commit/ed7a0919ab6b31184dc4d686b722dbeb013e9ce9) - Expand and collapse dataframe cells. Thanks @hannahblair!
9
+ - [#10478](https://github.com/gradio-app/gradio/pull/10478) [`afb96c6`](https://github.com/gradio-app/gradio/commit/afb96c64451e5a282bfee89445d831d1c87f9746) - Improve dataframe's upload accessibility. Thanks @hannahblair!
10
+ - [#10491](https://github.com/gradio-app/gradio/pull/10491) [`ff5f976`](https://github.com/gradio-app/gradio/commit/ff5f976bbb685fdd4f7c1faeda79e094f55a9f56) - Allow multiline headers in gr.Dataframe. Thanks @hannahblair!
11
+ - [#10494](https://github.com/gradio-app/gradio/pull/10494) [`10932a2`](https://github.com/gradio-app/gradio/commit/10932a291ac7f591bb1d56e4e353b51f10ecc6e3) - Ensure dataframe is not editable when `interactive` is False. Thanks @hannahblair!
12
+
13
+ ### Dependency updates
14
+
15
+ - @gradio/client@1.11.0
16
+ - @gradio/upload@0.15.0
17
+ - @gradio/button@0.4.5
18
+
19
+ ## 0.14.0
20
+
21
+ ### Features
22
+
23
+ - [#10461](https://github.com/gradio-app/gradio/pull/10461) [`ca7c47e`](https://github.com/gradio-app/gradio/commit/ca7c47e5e50a309cd637c4f928ab90af6355b01d) - Add copy button to dataframe toolbar. Thanks @hannahblair!
24
+ - [#10420](https://github.com/gradio-app/gradio/pull/10420) [`a69b8e8`](https://github.com/gradio-app/gradio/commit/a69b8e83ad7c89c627db2bdd5d25b0142731aaac) - Support column/row deletion in `gr.DataFrame`. Thanks @abidlabs!
25
+
26
+ ### Dependency updates
27
+
28
+ - @gradio/upload@0.14.8
29
+ - @gradio/button@0.4.4
30
+
3
31
  ## 0.13.1
4
32
 
5
33
  ### Features
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ // @ts-nocheck
2
3
  import { Meta, Template, Story } from "@storybook/addon-svelte-csf";
3
4
  import Table from "./shared/Table.svelte";
4
5
  import { within } from "@testing-library/dom";
@@ -10,6 +11,11 @@
10
11
  <Meta
11
12
  title="Components/DataFrame"
12
13
  component={Table}
14
+ parameters={{
15
+ test: {
16
+ dangerouslyIgnoreUnhandledErrors: true // ignore fullscreen permission error
17
+ }
18
+ }}
13
19
  argTypes={{
14
20
  editable: {
15
21
  control: [true, false],
@@ -87,6 +93,26 @@
87
93
  row_count: [3, "dynamic"],
88
94
  editable: false
89
95
  }}
96
+ play={async ({ canvasElement }) => {
97
+ // tests that the cell is not editable
98
+
99
+ const canvas = within(canvasElement);
100
+ const cells = canvas.getAllByRole("cell");
101
+ const initial_value = cells[0].textContent;
102
+
103
+ await userEvent.click(cells[0]);
104
+ await userEvent.keyboard("new value");
105
+
106
+ const final_value = cells[0].textContent;
107
+ if (initial_value !== final_value) {
108
+ throw new Error("Cell content changed when it should be non-editable");
109
+ }
110
+
111
+ const inputs = canvas.queryAllByRole("textbox");
112
+ if (inputs.length > 0) {
113
+ throw new Error("Input field appeared when table should be non-editable");
114
+ }
115
+ }}
90
116
  />
91
117
 
92
118
  <Story
@@ -194,9 +220,11 @@
194
220
  const canvas = within(canvasElement);
195
221
 
196
222
  const cell_400 = canvas.getAllByRole("cell")[5];
197
- userEvent.click(cell_400);
223
+ await userEvent.click(cell_400);
198
224
 
199
- const open_dialog_btn = within(cell_400).getByText("");
225
+ const open_dialog_btn = await within(cell_400).findByRole("button", {
226
+ name: "⋮"
227
+ });
200
228
  await userEvent.click(open_dialog_btn);
201
229
 
202
230
  const add_row_btn = canvas.getByText("Add row above");
@@ -220,3 +248,121 @@
220
248
  show_fullscreen_button: true
221
249
  }}
222
250
  />
251
+
252
+ <Story
253
+ name="Dataframe with multiple selection interactions"
254
+ args={{
255
+ values: [
256
+ [1, 2, 3, 4],
257
+ [5, 6, 7, 8],
258
+ [9, 10, 11, 12],
259
+ [13, 14, 15, 16]
260
+ ],
261
+ col_count: [4, "dynamic"],
262
+ row_count: [4, "dynamic"],
263
+ headers: ["A", "B", "C", "D"],
264
+ editable: true
265
+ }}
266
+ play={async ({ canvasElement }) => {
267
+ const canvas = within(canvasElement);
268
+ const cells = canvas.getAllByRole("cell");
269
+ const user = userEvent.setup();
270
+
271
+ // cmd+click to select non-contiguous cells
272
+ await user.keyboard("[MetaLeft>]");
273
+ await user.click(cells[4]);
274
+ await user.click(cells[6]);
275
+ await user.click(cells[2]);
276
+ await user.keyboard("[/MetaLeft]");
277
+
278
+ // shift+click to select a range
279
+ await user.keyboard("[ShiftLeft>]");
280
+ await user.click(cells[7]);
281
+ await user.click(cells[6]);
282
+ await user.keyboard("[/ShiftLeft]");
283
+
284
+ // clear selected cells
285
+ await user.keyboard("{Delete}");
286
+
287
+ // verify cells were cleared by clicking one
288
+ await user.click(cells[2]);
289
+ }}
290
+ />
291
+
292
+ <Story
293
+ name="Dataframe toolbar interactions"
294
+ args={{
295
+ col_count: [3, "dynamic"],
296
+ row_count: [2, "dynamic"],
297
+ headers: ["Math", "Reading", "Writing"],
298
+ values: [
299
+ [800, 100, 400],
300
+ [200, 800, 700]
301
+ ],
302
+ show_fullscreen_button: true,
303
+ show_copy_button: true
304
+ }}
305
+ play={async ({ canvasElement }) => {
306
+ const canvas = within(canvasElement);
307
+
308
+ const copy_button = canvas.getByRole("button", {
309
+ name: "Copy table data"
310
+ });
311
+ await userEvent.click(copy_button);
312
+
313
+ const fullscreen_button = canvas.getByRole("button", {
314
+ name: "Enter fullscreen"
315
+ });
316
+ await userEvent.click(fullscreen_button);
317
+
318
+ await userEvent.click(fullscreen_button);
319
+ }}
320
+ />
321
+
322
+ <Story
323
+ name="Dataframe with truncated text"
324
+ args={{
325
+ values: [
326
+ [
327
+ "This is a very long text that should be truncated",
328
+ "Short text",
329
+ "Another very long text that needs truncation"
330
+ ],
331
+ [
332
+ "Short",
333
+ "This text is also quite long and should be truncated as well",
334
+ "Medium length text here"
335
+ ],
336
+ [
337
+ "Medium text",
338
+ "Brief",
339
+ "This is the longest text in the entire table and it should definitely be truncated"
340
+ ]
341
+ ],
342
+ headers: ["Column A", "Column B", "Column C"],
343
+ label: "Truncated Text Example",
344
+ max_chars: 20,
345
+ col_count: [3, "dynamic"],
346
+ row_count: [3, "dynamic"]
347
+ }}
348
+ />
349
+
350
+ <Story
351
+ name="Dataframe with multiline headers"
352
+ args={{
353
+ values: [
354
+ [95, 92, 88],
355
+ [89, 90, 85],
356
+ [92, 88, 91]
357
+ ],
358
+ headers: [
359
+ "Dataset A\nAccuracy",
360
+ "Dataset B\nPrecision",
361
+ "Dataset C\nRecall"
362
+ ],
363
+ label: "Model Metrics",
364
+ col_count: [3, "dynamic"],
365
+ row_count: [3, "dynamic"],
366
+ editable: false
367
+ }}
368
+ />
package/Index.svelte CHANGED
@@ -49,6 +49,8 @@
49
49
  export let loading_status: LoadingStatus;
50
50
  export let interactive: boolean;
51
51
  export let show_fullscreen_button = false;
52
+ export let max_chars: number | undefined = undefined;
53
+ export let show_copy_button = false;
52
54
 
53
55
  $: _headers = [...(value.headers || headers)];
54
56
  $: cell_values = value.data ? [...value.data] : [];
@@ -105,5 +107,7 @@
105
107
  stream_handler={(...args) => gradio.client.stream(...args)}
106
108
  bind:value_is_output
107
109
  {show_fullscreen_button}
110
+ {max_chars}
111
+ {show_copy_button}
108
112
  />
109
113
  </Block>
package/dist/Index.svelte CHANGED
@@ -34,6 +34,8 @@ export let max_height = void 0;
34
34
  export let loading_status;
35
35
  export let interactive;
36
36
  export let show_fullscreen_button = false;
37
+ export let max_chars = void 0;
38
+ export let show_copy_button = false;
37
39
  $:
38
40
  _headers = [...value.headers || headers];
39
41
  $:
@@ -88,5 +90,7 @@ $:
88
90
  stream_handler={(...args) => gradio.client.stream(...args)}
89
91
  bind:value_is_output
90
92
  {show_fullscreen_button}
93
+ {max_chars}
94
+ {show_copy_button}
91
95
  />
92
96
  </Block>
@@ -38,6 +38,8 @@ declare const __propDef: {
38
38
  loading_status: LoadingStatus;
39
39
  interactive: boolean;
40
40
  show_fullscreen_button?: boolean | undefined;
41
+ max_chars?: number | undefined;
42
+ show_copy_button?: boolean | undefined;
41
43
  };
42
44
  events: {
43
45
  [evt: string]: CustomEvent<any>;
@@ -135,4 +137,10 @@ export default class Index extends SvelteComponent<IndexProps, IndexEvents, Inde
135
137
  get show_fullscreen_button(): boolean | undefined;
136
138
  /**accessor*/
137
139
  set show_fullscreen_button(_: boolean | undefined);
140
+ get max_chars(): number | undefined;
141
+ /**accessor*/
142
+ set max_chars(_: number | undefined);
143
+ get show_copy_button(): boolean | undefined;
144
+ /**accessor*/
145
+ set show_copy_button(_: boolean | undefined);
138
146
  }
@@ -1,5 +1,5 @@
1
1
  <script>import { onMount } from "svelte";
2
- import Arrow from "./Arrow.svelte";
2
+ import CellMenuIcons from "./CellMenuIcons.svelte";
3
3
  export let x;
4
4
  export let y;
5
5
  export let on_add_row_above;
@@ -9,6 +9,10 @@ export let on_add_column_right;
9
9
  export let row;
10
10
  export let col_count;
11
11
  export let row_count;
12
+ export let on_delete_row;
13
+ export let on_delete_col;
14
+ export let can_delete_rows;
15
+ export let can_delete_cols;
12
16
  export let i18n;
13
17
  let menu_element;
14
18
  $:
@@ -42,23 +46,35 @@ function position_menu() {
42
46
  <div bind:this={menu_element} class="cell-menu">
43
47
  {#if !is_header && can_add_rows}
44
48
  <button on:click={() => on_add_row_above()}>
45
- <Arrow transform="rotate(-90 12 12)" />
49
+ <CellMenuIcons icon="add-row-above" />
46
50
  {i18n("dataframe.add_row_above")}
47
51
  </button>
48
52
  <button on:click={() => on_add_row_below()}>
49
- <Arrow transform="rotate(90 12 12)" />
53
+ <CellMenuIcons icon="add-row-below" />
50
54
  {i18n("dataframe.add_row_below")}
51
55
  </button>
56
+ {#if can_delete_rows}
57
+ <button on:click={on_delete_row} class="delete">
58
+ <CellMenuIcons icon="delete-row" />
59
+ {i18n("dataframe.delete_row")}
60
+ </button>
61
+ {/if}
52
62
  {/if}
53
63
  {#if can_add_columns}
54
64
  <button on:click={() => on_add_column_left()}>
55
- <Arrow transform="rotate(180 12 12)" />
65
+ <CellMenuIcons icon="add-column-left" />
56
66
  {i18n("dataframe.add_column_left")}
57
67
  </button>
58
68
  <button on:click={() => on_add_column_right()}>
59
- <Arrow transform="rotate(0 12 12)" />
69
+ <CellMenuIcons icon="add-column-right" />
60
70
  {i18n("dataframe.add_column_right")}
61
71
  </button>
72
+ {#if can_delete_cols}
73
+ <button on:click={on_delete_col} class="delete">
74
+ <CellMenuIcons icon="delete-column" />
75
+ {i18n("dataframe.delete_column")}
76
+ </button>
77
+ {/if}
62
78
  {/if}
63
79
  </div>
64
80
 
@@ -102,8 +118,4 @@ function position_menu() {
102
118
  fill: currentColor;
103
119
  transition: fill 0.2s;
104
120
  }
105
-
106
- .cell-menu button:hover :global(svg) {
107
- fill: var(--color-accent);
108
- }
109
121
  </style>
@@ -11,6 +11,10 @@ declare const __propDef: {
11
11
  row: number;
12
12
  col_count: [number, "fixed" | "dynamic"];
13
13
  row_count: [number, "fixed" | "dynamic"];
14
+ on_delete_row: () => void;
15
+ on_delete_col: () => void;
16
+ can_delete_rows: boolean;
17
+ can_delete_cols: boolean;
14
18
  i18n: I18nFormatter;
15
19
  };
16
20
  events: {
@@ -0,0 +1,112 @@
1
+ <script>export let icon;
2
+ </script>
3
+
4
+ {#if icon == "add-column-right"}
5
+ <svg viewBox="0 0 24 24" width="16" height="16">
6
+ <rect
7
+ x="4"
8
+ y="6"
9
+ width="4"
10
+ height="12"
11
+ stroke="currentColor"
12
+ stroke-width="2"
13
+ fill="none"
14
+ />
15
+ <path
16
+ d="M12 12H19M16 8L19 12L16 16"
17
+ stroke="currentColor"
18
+ stroke-width="2"
19
+ fill="none"
20
+ stroke-linecap="round"
21
+ />
22
+ </svg>
23
+ {:else if icon == "add-column-left"}
24
+ <svg viewBox="0 0 24 24" width="16" height="16">
25
+ <rect
26
+ x="16"
27
+ y="6"
28
+ width="4"
29
+ height="12"
30
+ stroke="currentColor"
31
+ stroke-width="2"
32
+ fill="none"
33
+ />
34
+ <path
35
+ d="M12 12H5M8 8L5 12L8 16"
36
+ stroke="currentColor"
37
+ stroke-width="2"
38
+ fill="none"
39
+ stroke-linecap="round"
40
+ />
41
+ </svg>
42
+ {:else if icon == "add-row-above"}
43
+ <svg viewBox="0 0 24 24" width="16" height="16">
44
+ <rect
45
+ x="6"
46
+ y="16"
47
+ width="12"
48
+ height="4"
49
+ stroke="currentColor"
50
+ stroke-width="2"
51
+ />
52
+ <path
53
+ d="M12 12V5M8 8L12 5L16 8"
54
+ stroke="currentColor"
55
+ stroke-width="2"
56
+ fill="none"
57
+ stroke-linecap="round"
58
+ />
59
+ </svg>
60
+ {:else if icon == "add-row-below"}
61
+ <svg viewBox="0 0 24 24" width="16" height="16">
62
+ <rect
63
+ x="6"
64
+ y="4"
65
+ width="12"
66
+ height="4"
67
+ stroke="currentColor"
68
+ stroke-width="2"
69
+ />
70
+ <path
71
+ d="M12 12V19M8 16L12 19L16 16"
72
+ stroke="currentColor"
73
+ stroke-width="2"
74
+ fill="none"
75
+ stroke-linecap="round"
76
+ />
77
+ </svg>
78
+ {:else if icon == "delete-row"}
79
+ <svg viewBox="0 0 24 24" width="16" height="16">
80
+ <rect
81
+ x="5"
82
+ y="10"
83
+ width="14"
84
+ height="4"
85
+ stroke="currentColor"
86
+ stroke-width="2"
87
+ />
88
+ <path
89
+ d="M8 7L16 17M16 7L8 17"
90
+ stroke="currentColor"
91
+ stroke-width="2"
92
+ stroke-linecap="round"
93
+ />
94
+ </svg>
95
+ {:else if icon == "delete-column"}
96
+ <svg viewBox="0 0 24 24" width="16" height="16">
97
+ <rect
98
+ x="10"
99
+ y="5"
100
+ width="4"
101
+ height="14"
102
+ stroke="currentColor"
103
+ stroke-width="2"
104
+ />
105
+ <path
106
+ d="M7 8L17 16M17 8L7 16"
107
+ stroke="currentColor"
108
+ stroke-width="2"
109
+ stroke-linecap="round"
110
+ />
111
+ </svg>
112
+ {/if}
@@ -0,0 +1,16 @@
1
+ import { SvelteComponent } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ icon: string;
5
+ };
6
+ events: {
7
+ [evt: string]: CustomEvent<any>;
8
+ };
9
+ slots: {};
10
+ };
11
+ export type CellMenuIconsProps = typeof __propDef.props;
12
+ export type CellMenuIconsEvents = typeof __propDef.events;
13
+ export type CellMenuIconsSlots = typeof __propDef.slots;
14
+ export default class CellMenuIcons extends SvelteComponent<CellMenuIconsProps, CellMenuIconsEvents, CellMenuIconsSlots> {
15
+ }
16
+ export {};
@@ -11,10 +11,20 @@ export let clear_on_focus = false;
11
11
  export let line_breaks = true;
12
12
  export let editable = true;
13
13
  export let root;
14
+ export let max_chars = null;
14
15
  const dispatch = createEventDispatcher();
16
+ let is_expanded = false;
15
17
  export let el;
16
18
  $:
17
19
  _value = value;
20
+ function truncate_text(text, max_length = null) {
21
+ const str = String(text);
22
+ if (!max_length || str.length <= max_length)
23
+ return str;
24
+ return str.slice(0, max_length) + "...";
25
+ }
26
+ $:
27
+ display_text = is_expanded ? value : truncate_text(display_value || value, max_chars);
18
28
  function use_focus(node) {
19
29
  if (clear_on_focus) {
20
30
  _value = "";
@@ -32,11 +42,20 @@ function handle_blur({
32
42
  }
33
43
  function handle_keydown(event) {
34
44
  if (event.key === "Enter") {
35
- value = _value;
36
- dispatch("blur");
45
+ if (edit) {
46
+ value = _value;
47
+ dispatch("blur");
48
+ } else if (!header) {
49
+ is_expanded = !is_expanded;
50
+ }
37
51
  }
38
52
  dispatch("keydown", event);
39
53
  }
54
+ function handle_click() {
55
+ if (!edit && !header) {
56
+ is_expanded = !is_expanded;
57
+ }
58
+ }
40
59
  </script>
41
60
 
42
61
  {#if edit}
@@ -56,27 +75,31 @@ function handle_keydown(event) {
56
75
  {/if}
57
76
 
58
77
  <span
59
- on:dblclick
60
- tabindex="-1"
78
+ on:click={handle_click}
79
+ on:keydown={handle_keydown}
80
+ tabindex="0"
61
81
  role="button"
62
82
  class:edit
83
+ class:expanded={is_expanded}
84
+ class:multiline={header}
63
85
  on:focus|preventDefault
64
86
  style={styling}
65
87
  class="table-cell-text"
88
+ data-editable={editable}
66
89
  placeholder=" "
67
90
  >
68
91
  {#if datatype === "html"}
69
- {@html value}
92
+ {@html display_text}
70
93
  {:else if datatype === "markdown"}
71
94
  <MarkdownCode
72
- message={value.toLocaleString()}
95
+ message={display_text.toLocaleString()}
73
96
  {latex_delimiters}
74
97
  {line_breaks}
75
98
  chatbot={false}
76
99
  {root}
77
100
  />
78
101
  {:else}
79
- {editable ? value : display_value || value}
102
+ {editable ? display_text : display_value || display_text}
80
103
  {/if}
81
104
  </span>
82
105
 
@@ -97,6 +120,8 @@ function handle_keydown(event) {
97
120
 
98
121
  span {
99
122
  flex: 1 1 0%;
123
+ position: relative;
124
+ display: inline-block;
100
125
  outline: none;
101
126
  padding: var(--size-2);
102
127
  -webkit-user-select: text;
@@ -104,11 +129,31 @@ function handle_keydown(event) {
104
129
  -ms-user-select: text;
105
130
  user-select: text;
106
131
  cursor: text;
132
+ width: 100%;
133
+ height: 100%;
134
+ }
135
+
136
+ input:where(:not(.header), [data-editable="true"]) {
137
+ width: calc(100% - var(--size-10));
138
+ }
139
+
140
+ span.expanded {
141
+ height: auto;
142
+ min-height: 100%;
143
+ white-space: pre-wrap;
144
+ word-break: break-word;
145
+ white-space: normal;
146
+ }
147
+
148
+ .multiline {
149
+ white-space: pre-line;
107
150
  }
108
151
 
109
152
  .header {
110
153
  transform: translateX(0);
111
154
  font-weight: var(--weight-bold);
155
+ white-space: normal;
156
+ word-break: break-word;
112
157
  }
113
158
 
114
159
  .edit {
@@ -16,13 +16,13 @@ declare const __propDef: {
16
16
  line_breaks?: boolean | undefined;
17
17
  editable?: boolean | undefined;
18
18
  root: string;
19
+ max_chars?: (number | null) | undefined;
19
20
  el: HTMLInputElement | null;
20
21
  };
21
22
  events: {
22
23
  mousedown: MouseEvent;
23
24
  mouseup: MouseEvent;
24
25
  click: MouseEvent;
25
- dblclick: MouseEvent;
26
26
  focus: FocusEvent;
27
27
  blur: CustomEvent<any>;
28
28
  keydown: CustomEvent<any>;