@gradio/dataframe 0.14.0 → 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,21 @@
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
+
3
19
  ## 0.14.0
4
20
 
5
21
  ### Features
@@ -93,6 +93,26 @@
93
93
  row_count: [3, "dynamic"],
94
94
  editable: false
95
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
+ }}
96
116
  />
97
117
 
98
118
  <Story
@@ -200,9 +220,11 @@
200
220
  const canvas = within(canvasElement);
201
221
 
202
222
  const cell_400 = canvas.getAllByRole("cell")[5];
203
- userEvent.click(cell_400);
223
+ await userEvent.click(cell_400);
204
224
 
205
- const open_dialog_btn = within(cell_400).getByText("");
225
+ const open_dialog_btn = await within(cell_400).findByRole("button", {
226
+ name: "⋮"
227
+ });
206
228
  await userEvent.click(open_dialog_btn);
207
229
 
208
230
  const add_row_btn = canvas.getByText("Add row above");
@@ -227,12 +249,52 @@
227
249
  }}
228
250
  />
229
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
+
230
292
  <Story
231
293
  name="Dataframe toolbar interactions"
232
294
  args={{
233
295
  col_count: [3, "dynamic"],
234
296
  row_count: [2, "dynamic"],
235
- headers: ["Math", "Reading", "Writifdsfsng"],
297
+ headers: ["Math", "Reading", "Writing"],
236
298
  values: [
237
299
  [800, 100, 400],
238
300
  [200, 800, 700]
@@ -244,15 +306,63 @@
244
306
  const canvas = within(canvasElement);
245
307
 
246
308
  const copy_button = canvas.getByRole("button", {
247
- name: /copy table data/i
309
+ name: "Copy table data"
248
310
  });
249
311
  await userEvent.click(copy_button);
250
312
 
251
313
  const fullscreen_button = canvas.getByRole("button", {
252
- name: /enter fullscreen/i
314
+ name: "Enter fullscreen"
253
315
  });
254
316
  await userEvent.click(fullscreen_button);
255
317
 
256
318
  await userEvent.click(fullscreen_button);
257
319
  }}
258
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,7 @@
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;
52
53
  export let show_copy_button = false;
53
54
 
54
55
  $: _headers = [...(value.headers || headers)];
@@ -106,6 +107,7 @@
106
107
  stream_handler={(...args) => gradio.client.stream(...args)}
107
108
  bind:value_is_output
108
109
  {show_fullscreen_button}
110
+ {max_chars}
109
111
  {show_copy_button}
110
112
  />
111
113
  </Block>
package/dist/Index.svelte CHANGED
@@ -34,6 +34,7 @@ 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;
37
38
  export let show_copy_button = false;
38
39
  $:
39
40
  _headers = [...value.headers || headers];
@@ -89,6 +90,7 @@ $:
89
90
  stream_handler={(...args) => gradio.client.stream(...args)}
90
91
  bind:value_is_output
91
92
  {show_fullscreen_button}
93
+ {max_chars}
92
94
  {show_copy_button}
93
95
  />
94
96
  </Block>
@@ -38,6 +38,7 @@ declare const __propDef: {
38
38
  loading_status: LoadingStatus;
39
39
  interactive: boolean;
40
40
  show_fullscreen_button?: boolean | undefined;
41
+ max_chars?: number | undefined;
41
42
  show_copy_button?: boolean | undefined;
42
43
  };
43
44
  events: {
@@ -136,6 +137,9 @@ export default class Index extends SvelteComponent<IndexProps, IndexEvents, Inde
136
137
  get show_fullscreen_button(): boolean | undefined;
137
138
  /**accessor*/
138
139
  set show_fullscreen_button(_: boolean | undefined);
140
+ get max_chars(): number | undefined;
141
+ /**accessor*/
142
+ set max_chars(_: number | undefined);
139
143
  get show_copy_button(): boolean | undefined;
140
144
  /**accessor*/
141
145
  set show_copy_button(_: boolean | undefined);
@@ -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>;