@gradio/dataframe 0.23.1 → 0.23.3

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.
@@ -5,6 +5,7 @@
5
5
  import FilterIcon from "./icons/FilterIcon.svelte";
6
6
  import SortButtonUp from "./icons/SortButtonUp.svelte";
7
7
  import SortButtonDown from "./icons/SortButtonDown.svelte";
8
+ import { BaseCheckbox } from "@gradio/checkbox";
8
9
  import type { I18nFormatter } from "js/core/src/gradio_helper";
9
10
  import type { SortDirection } from "./types";
10
11
 
@@ -14,6 +15,8 @@
14
15
  is_editing = false,
15
16
  is_selected = false,
16
17
  is_static = false,
18
+ is_bool = false,
19
+ select_all_state = "unchecked",
17
20
  sort_direction = null,
18
21
  sort_priority = null,
19
22
  multi_sort = false,
@@ -25,15 +28,19 @@
25
28
  editable = true,
26
29
  max_chars = undefined,
27
30
  i18n,
31
+ wrap_text = false,
28
32
  onclick,
29
33
  on_menu_click,
30
- on_end_edit
34
+ on_end_edit,
35
+ on_select_all
31
36
  }: {
32
37
  value: string;
33
38
  col_idx: number;
34
39
  is_editing?: boolean;
35
40
  is_selected?: boolean;
36
41
  is_static?: boolean;
42
+ is_bool?: boolean;
43
+ select_all_state?: "checked" | "unchecked" | "indeterminate";
37
44
  sort_direction?: SortDirection | null;
38
45
  sort_priority?: number | null;
39
46
  multi_sort?: boolean;
@@ -45,10 +52,16 @@
45
52
  editable?: boolean;
46
53
  max_chars?: number | undefined;
47
54
  i18n: I18nFormatter;
55
+ wrap_text?: boolean;
48
56
  onclick: (event: MouseEvent, col: number) => void;
49
57
  on_menu_click: (event: MouseEvent, col: number) => void;
50
58
  on_end_edit: (key: string) => void;
59
+ on_select_all?: (col: number, checked: boolean) => void;
51
60
  } = $props();
61
+
62
+ let show_select_all = $derived(
63
+ is_bool && editable && !is_static && !!on_select_all
64
+ );
52
65
  </script>
53
66
 
54
67
  <th
@@ -57,6 +70,7 @@
57
70
  class:sorted={sort_direction !== null}
58
71
  class:filtered={is_filtered}
59
72
  class:first-column={is_first_column}
73
+ data-heading={col_idx}
60
74
  onclick={(e) => onclick(e, col_idx)}
61
75
  onmousedown={(e) => {
62
76
  e.preventDefault();
@@ -66,6 +80,25 @@
66
80
  >
67
81
  <div class="cell-wrap">
68
82
  <div class="header-content">
83
+ {#if show_select_all}
84
+ <div
85
+ class="select-all-checkbox"
86
+ role="button"
87
+ tabindex="-1"
88
+ onclick={(e) => e.stopPropagation()}
89
+ onmousedown={(e) => e.stopPropagation()}
90
+ onkeydown={(e) => e.stopPropagation()}
91
+ >
92
+ <BaseCheckbox
93
+ value={select_all_state === "checked"}
94
+ indeterminate={select_all_state === "indeterminate"}
95
+ label={`Toggle all: ${value}`}
96
+ interactive={true}
97
+ on_select={() =>
98
+ on_select_all?.(col_idx, select_all_state !== "checked")}
99
+ />
100
+ </div>
101
+ {/if}
69
102
  <EditableCell
70
103
  {value}
71
104
  {latex_delimiters}
@@ -81,7 +114,9 @@
81
114
  {is_static}
82
115
  {i18n}
83
116
  {max_chars}
117
+ {wrap_text}
84
118
  coords={[col_idx, 0]}
119
+ pad_left={show_select_all ? true : false}
85
120
  />
86
121
  </div>
87
122
  {#if sort_direction || is_filtered || is_static}
@@ -129,6 +164,13 @@
129
164
  padding: 0;
130
165
  background: var(--table-even-background-fill) !important;
131
166
  font-weight: var(--weight-bold, 700);
167
+ outline: none;
168
+ box-shadow:
169
+ inset 1px 0 0 var(--border-color-primary),
170
+ inset 0 0 0 1px var(--ring-color);
171
+ padding: 0;
172
+ overflow: visible;
173
+ box-sizing: border-box;
132
174
  }
133
175
 
134
176
  .header-cell.first-column {
@@ -142,10 +184,14 @@
142
184
 
143
185
  .header-cell.focus {
144
186
  --ring-color: var(--color-accent);
187
+ --sel-top: inset 0 2px 0 0 var(--ring-color);
188
+ --sel-bottom: inset 0 -2px 0 0 var(--ring-color);
189
+ --sel-left: inset 2px 0 0 0 var(--ring-color);
190
+ --sel-right: inset -2px 0 0 0 var(--ring-color);
145
191
  box-shadow:
146
- inset 1px 0 0 var(--border-color-primary),
147
- inset 0 0 0 2px var(--ring-color);
148
- z-index: 4;
192
+ var(--sel-top), var(--sel-bottom), var(--sel-left), var(--sel-right);
193
+ z-index: 2;
194
+ position: relative;
149
195
  }
150
196
 
151
197
  .header-cell.focus.first-column {
@@ -155,7 +201,7 @@
155
201
  .header-content {
156
202
  display: flex;
157
203
  align-items: center;
158
- gap: var(--size-1);
204
+ gap: var(--size-3);
159
205
  overflow: hidden;
160
206
  min-width: 0;
161
207
  }
@@ -215,4 +261,20 @@
215
261
  overflow: visible;
216
262
  min-width: 0;
217
263
  }
264
+
265
+ .select-all-checkbox {
266
+ display: flex;
267
+ align-items: center;
268
+ justify-content: center;
269
+ flex-shrink: 0;
270
+ width: var(--size-4);
271
+ }
272
+
273
+ .select-all-checkbox :global(label) {
274
+ margin: 0;
275
+ }
276
+
277
+ .select-all-checkbox :global(span) {
278
+ display: none;
279
+ }
218
280
  </style>
@@ -6,6 +6,8 @@ type $$ComponentProps = {
6
6
  is_editing?: boolean;
7
7
  is_selected?: boolean;
8
8
  is_static?: boolean;
9
+ is_bool?: boolean;
10
+ select_all_state?: "checked" | "unchecked" | "indeterminate";
9
11
  sort_direction?: SortDirection | null;
10
12
  sort_priority?: number | null;
11
13
  multi_sort?: boolean;
@@ -21,9 +23,11 @@ type $$ComponentProps = {
21
23
  editable?: boolean;
22
24
  max_chars?: number | undefined;
23
25
  i18n: I18nFormatter;
26
+ wrap_text?: boolean;
24
27
  onclick: (event: MouseEvent, col: number) => void;
25
28
  on_menu_click: (event: MouseEvent, col: number) => void;
26
29
  on_end_edit: (key: string) => void;
30
+ on_select_all?: (col: number, checked: boolean) => void;
27
31
  };
28
32
  declare const HeaderCell: import("svelte").Component<$$ComponentProps, {}, "">;
29
33
  type HeaderCell = ReturnType<typeof HeaderCell>;
@@ -13,6 +13,7 @@
13
13
  import { tick, onMount } from "svelte";
14
14
  import { Upload } from "@gradio/upload";
15
15
 
16
+ import { MarkdownCode } from "@gradio/markdown-code";
16
17
  import HeaderCell from "./HeaderCell.svelte";
17
18
  import DataCell from "./DataCell.svelte";
18
19
  import EmptyRowButton from "./EmptyRowButton.svelte";
@@ -147,7 +148,7 @@
147
148
  id: `col_${j}`,
148
149
  accessorKey: `col_${j}`,
149
150
  header: header_value,
150
- size: column_widths[j] ? parseInt(column_widths[j]) || 150 : 150,
151
+ size: 150,
151
152
  minSize: 45,
152
153
  filterFn: gradio_filter_fn,
153
154
  meta: {
@@ -263,6 +264,82 @@
263
264
  return Array.isArray(datatype) ? (datatype[col] ?? "str") : datatype;
264
265
  }
265
266
 
267
+ type SizingEntry = { val: string; col_idx: number; dtype: Datatype };
268
+
269
+ // heading multipliers for markdown/html block elements that render
270
+ // at a larger font size than body text.
271
+ const HEADING_MULT = [2.2, 1.7, 1.4, 1.2, 1.1, 1.05];
272
+
273
+ function md_visual_length(s: string): number {
274
+ const heading = s.match(/^\s*(#{1,6})\s+(.+)/);
275
+ if (heading) {
276
+ const lvl = heading[1].length;
277
+ const text = heading[2].replace(/[*_`[\]()]/g, "");
278
+ return text.length * HEADING_MULT[lvl - 1];
279
+ }
280
+ return s.replace(/[*_`#[\]()]/g, "").length;
281
+ }
282
+
283
+ function html_visual_length(s: string): number {
284
+ const stripped = s.replace(/<[^>]+>/g, "").length;
285
+ const h = s.match(/<h([1-6])\b/i);
286
+ if (h) return stripped * HEADING_MULT[parseInt(h[1]) - 1];
287
+ return stripped;
288
+ }
289
+
290
+ // mirror EditableCell.truncate_text so the sizing row reserves width
291
+ // for the truncated ("…") string, not the full source
292
+ function apply_truncation(s: string, dtype: Datatype): string {
293
+ if (
294
+ max_chars &&
295
+ max_chars > 0 &&
296
+ dtype !== "image" &&
297
+ s.length > max_chars
298
+ ) {
299
+ return s.slice(0, max_chars) + "...";
300
+ }
301
+ return s;
302
+ }
303
+
304
+ // find the widest rendered value per visible column for the sizing row
305
+ function compute_sizing_row(): SizingEntry[] {
306
+ const headers = header_groups[0]?.headers ?? [];
307
+ return headers.map((header) => {
308
+ const col_idx = (header.column.columnDef.meta as any)?.colIndex ?? 0;
309
+ const dtype = get_dtype(col_idx);
310
+ const accessor = `col_${col_idx}`;
311
+
312
+ if (dtype === "bool") {
313
+ return { val: "false", col_idx, dtype };
314
+ }
315
+
316
+ const visual_len =
317
+ dtype === "markdown"
318
+ ? md_visual_length
319
+ : dtype === "html"
320
+ ? html_visual_length
321
+ : (s: string) => s.length;
322
+
323
+ let best = "";
324
+ let best_len = -1;
325
+ for (const r of rows) {
326
+ const row_idx = r.original._index;
327
+ const rendered = editable
328
+ ? r.original[accessor]
329
+ : (display_value?.[row_idx]?.[col_idx] ??
330
+ values?.[row_idx]?.[col_idx]);
331
+ if (rendered == null) continue;
332
+ const v = apply_truncation(String(rendered), dtype);
333
+ const len = visual_len(v);
334
+ if (len > best_len) {
335
+ best_len = len;
336
+ best = v;
337
+ }
338
+ }
339
+ return { val: best, col_idx, dtype };
340
+ });
341
+ }
342
+
266
343
  function get_display_value(row: number, col: number): string {
267
344
  if (display_value?.[row]?.[col] !== undefined)
268
345
  return display_value[row][col];
@@ -291,7 +368,13 @@
291
368
  row: number,
292
369
  col: number
293
370
  ): void {
294
- event.preventDefault();
371
+ const col_is_static =
372
+ !editable ||
373
+ static_columns.includes(col) ||
374
+ static_columns.includes(resolved_headers[col]);
375
+ if (!col_is_static) {
376
+ event.preventDefault();
377
+ }
295
378
  event.stopPropagation();
296
379
 
297
380
  const coord: CellCoordinate = [row, col];
@@ -332,7 +415,9 @@
332
415
  col_value: values?.map((r) => r[col]) ?? []
333
416
  } as any);
334
417
 
335
- tick().then(() => parent?.focus());
418
+ if (!col_is_static) {
419
+ tick().then(() => parent?.focus());
420
+ }
336
421
  }
337
422
 
338
423
  function handle_cell_dblclick(
@@ -340,15 +425,14 @@
340
425
  row: number,
341
426
  col: number
342
427
  ): void {
343
- event.preventDefault();
344
- event.stopPropagation();
345
- if (!editable) return;
346
428
  const col_is_static =
429
+ !editable ||
347
430
  static_columns.includes(col) ||
348
431
  static_columns.includes(resolved_headers[col]);
349
- if (!col_is_static) {
350
- editing = [row, col];
351
- }
432
+ if (col_is_static) return;
433
+ event.preventDefault();
434
+ event.stopPropagation();
435
+ editing = [row, col];
352
436
  }
353
437
 
354
438
  function handle_blur(detail: {
@@ -529,14 +613,29 @@
529
613
  active_cell_menu = null;
530
614
  }
531
615
 
532
- // function handle_select_all(col: number, checked: boolean): void {
533
- // values = values.map((row) => {
534
- // const new_row = [...row];
535
- // new_row[col] = checked;
536
- // return new_row;
537
- // });
538
- // push_change(values);
539
- // }
616
+ function handle_select_all(col: number, checked: boolean): void {
617
+ const new_values = values.map((row) => {
618
+ const new_row = [...row];
619
+ new_row[col] = checked;
620
+ return new_row;
621
+ });
622
+ values = new_values;
623
+ push_change(new_values);
624
+ }
625
+
626
+ function get_select_all_state(
627
+ col: number
628
+ ): "checked" | "unchecked" | "indeterminate" {
629
+ if (!values.length) return "unchecked";
630
+ let true_count = 0;
631
+ for (const row of values) {
632
+ const v = row[col];
633
+ if (v === true || v === "true" || v === "True") true_count++;
634
+ }
635
+ if (true_count === 0) return "unchecked";
636
+ if (true_count === values.length) return "checked";
637
+ return "indeterminate";
638
+ }
540
639
 
541
640
  function commit_filter(): void {
542
641
  if (!global_filter || show_search !== "filter") return;
@@ -581,13 +680,22 @@
581
680
  if (selected) {
582
681
  const [row, col] = selected;
583
682
 
683
+ if (
684
+ editing &&
685
+ (e.key === "ArrowUp" ||
686
+ e.key === "ArrowDown" ||
687
+ e.key === "ArrowLeft" ||
688
+ e.key === "ArrowRight")
689
+ ) {
690
+ return;
691
+ }
692
+
584
693
  switch (e.key) {
585
694
  case "ArrowUp":
586
695
  e.preventDefault();
587
696
  if (row > 0) {
588
697
  selected = [row - 1, col];
589
698
  selected_cells = [selected];
590
- editing = false;
591
699
  virtualizer.instance.scrollToIndex(row - 1, { align: "auto" });
592
700
  }
593
701
  break;
@@ -596,7 +704,6 @@
596
704
  if (row < num_rows - 1) {
597
705
  selected = [row + 1, col];
598
706
  selected_cells = [selected];
599
- editing = false;
600
707
  virtualizer.instance.scrollToIndex(row + 1, { align: "auto" });
601
708
  }
602
709
  break;
@@ -605,7 +712,6 @@
605
712
  if (col > 0) {
606
713
  selected = [row, col - 1];
607
714
  selected_cells = [selected];
608
- editing = false;
609
715
  }
610
716
  break;
611
717
  case "ArrowRight":
@@ -613,7 +719,6 @@
613
719
  if (col < num_cols - 1) {
614
720
  selected = [row, col + 1];
615
721
  selected_cells = [selected];
616
- editing = false;
617
722
  }
618
723
  break;
619
724
  case "Tab": {
@@ -758,6 +863,7 @@
758
863
 
759
864
  let header_row_el: HTMLTableRowElement;
760
865
  let header_table_el: HTMLTableElement;
866
+ let viewport_width = $state(0);
761
867
 
762
868
  const measurement = create_column_measurement({
763
869
  header_row_el: () => header_row_el,
@@ -766,6 +872,7 @@
766
872
  row_data: () => row_data,
767
873
  show_row_numbers: () => show_row_numbers,
768
874
  column_widths: () => column_widths,
875
+ viewport_width: () => viewport_width,
769
876
  on_resize: undefined
770
877
  });
771
878
 
@@ -828,11 +935,11 @@
828
935
  bind:this={parent}
829
936
  class="table-wrap"
830
937
  class:dragging={is_dragging}
831
- class:no-wrap={!wrap}
832
938
  class:menu-open={active_cell_menu || active_header_menu}
833
939
  onkeydown={handle_keydown}
834
940
  role="grid"
835
941
  tabindex="0"
942
+ style="--df-max-col-width: {viewport_width}px;"
836
943
  >
837
944
  <Upload
838
945
  {upload}
@@ -850,6 +957,7 @@
850
957
  class="virtual-table-viewport"
851
958
  class:disable-scroll={disable_scroll}
852
959
  bind:this={scroll_container}
960
+ bind:clientWidth={viewport_width}
853
961
  onscroll={handle_scroll}
854
962
  style="max-height: {max_height}px;"
855
963
  role="grid"
@@ -875,6 +983,8 @@
875
983
  is_selected={selected_header === col_idx}
876
984
  is_static={!!(header.column.columnDef.meta as any)
877
985
  ?.isStatic}
986
+ is_bool={get_dtype(col_idx) === "bool"}
987
+ select_all_state={get_select_all_state(col_idx)}
878
988
  sort_direction={get_sort_info(col_idx).direction}
879
989
  sort_priority={get_sort_info(col_idx).priority}
880
990
  multi_sort={sorting.length > 1}
@@ -886,36 +996,39 @@
886
996
  {editable}
887
997
  {max_chars}
888
998
  {i18n}
999
+ wrap_text={wrap}
889
1000
  onclick={handle_header_click}
890
1001
  on_menu_click={toggle_header_menu}
891
1002
  on_end_edit={end_header_edit}
1003
+ on_select_all={handle_select_all}
892
1004
  />
893
1005
  {/each}
894
1006
  {/each}
895
1007
  </tr>
896
1008
  </thead>
897
- <!-- hidden sizing row: lets table-layout:auto consider body content widths too -->
1009
+
898
1010
  <tbody class="sizing-body" aria-hidden="true">
899
1011
  {#if rows.length > 0}
900
- {@const sizing_row = rows.reduce((widest, row) => {
901
- const cells = row.getVisibleCells();
902
- cells.forEach((cell, i) => {
903
- const val = String(cell.getValue() ?? "");
904
- if (!widest[i] || val.length > widest[i].length) {
905
- widest[i] = val;
906
- }
907
- });
908
- return widest;
909
- }, [] as string[])}
1012
+ {@const sizing_row = compute_sizing_row()}
910
1013
  <tr>
911
1014
  {#if show_row_numbers}
912
1015
  <td class="row-number-cell">{rows.length}</td>
913
1016
  {/if}
914
- {#each sizing_row as val, ci}
915
- {@const dtype = get_dtype(ci)}
1017
+ {#each sizing_row as entry (entry.col_idx)}
916
1018
  <td
917
1019
  ><div class="cell-wrap">
918
- {#if dtype === "html" || dtype === "markdown"}{@html val}{:else}{val}{/if}
1020
+ {#if entry.dtype === "markdown"}
1021
+ <MarkdownCode
1022
+ message={entry.val}
1023
+ {latex_delimiters}
1024
+ {line_breaks}
1025
+ chatbot={false}
1026
+ />
1027
+ {:else if entry.dtype === "html"}
1028
+ {@html entry.val}
1029
+ {:else}
1030
+ {entry.val}
1031
+ {/if}
919
1032
  </div></td
920
1033
  >
921
1034
  {/each}
@@ -981,14 +1094,19 @@
981
1094
  editing[1] === col_idx
982
1095
  )}
983
1096
  is_flash={copy_flash && is_sel}
984
- is_static={!!(cell.column.columnDef.meta as any)?.isStatic}
1097
+ is_static={!editable ||
1098
+ !!(cell.column.columnDef.meta as any)?.isStatic}
985
1099
  show_menu_button={editable &&
1100
+ !(cell.column.columnDef.meta as any)?.isStatic &&
986
1101
  selected_cells.length === 1 &&
987
1102
  selected_cells[0][0] === row_idx &&
988
1103
  selected_cells[0][1] === col_idx}
989
1104
  show_selection_buttons={selected_cells.length === 1 &&
990
1105
  selected_cells[0][0] === row_idx &&
991
1106
  selected_cells[0][1] === col_idx}
1107
+ is_solo={selected_cells.length === 1 &&
1108
+ selected_cells[0][0] === row_idx &&
1109
+ selected_cells[0][1] === col_idx}
992
1110
  is_first_column={ci === 0 && !show_row_numbers}
993
1111
  {latex_delimiters}
994
1112
  {line_breaks}
@@ -1002,6 +1120,10 @@
1002
1120
  ondblclick={(e) =>
1003
1121
  handle_cell_dblclick(e, row_idx, col_idx)}
1004
1122
  oncontextmenu={(e) => {
1123
+ const is_static_cell = !!(
1124
+ cell.column.columnDef.meta as any
1125
+ )?.isStatic;
1126
+ if (!editable || is_static_cell) return;
1005
1127
  e.preventDefault();
1006
1128
  toggle_cell_menu(e, row_idx, col_idx);
1007
1129
  }}
@@ -1109,7 +1231,7 @@
1109
1231
  gap: var(--size-2);
1110
1232
  position: relative;
1111
1233
  max-width: 100%;
1112
- overflow: hidden;
1234
+ overflow-x: hidden;
1113
1235
  }
1114
1236
 
1115
1237
  .table-container.fullscreen {
@@ -1229,6 +1351,12 @@
1229
1351
  padding: var(--size-2);
1230
1352
  border: none;
1231
1353
  white-space: nowrap;
1354
+ max-width: var(--df-max-col-width);
1355
+ overflow: hidden;
1356
+ }
1357
+
1358
+ .header-table :global(.header-cell) {
1359
+ max-width: var(--df-max-col-width);
1232
1360
  }
1233
1361
 
1234
1362
  /* Virtual body */
@@ -1304,18 +1432,6 @@
1304
1432
  justify-content: center;
1305
1433
  }
1306
1434
 
1307
- .no-wrap {
1308
- white-space: nowrap;
1309
- }
1310
-
1311
- div:not(.no-wrap) :global(td) {
1312
- overflow-wrap: anywhere;
1313
- }
1314
-
1315
- div.no-wrap :global(td) {
1316
- overflow-x: hidden;
1317
- }
1318
-
1319
1435
  .header-row {
1320
1436
  display: flex;
1321
1437
  justify-content: flex-end;
@@ -9,6 +9,7 @@ export declare function create_column_measurement(opts: {
9
9
  row_data: () => any[];
10
10
  show_row_numbers: () => boolean;
11
11
  column_widths: () => string[];
12
+ viewport_width: () => number;
12
13
  on_resize?: () => void;
13
14
  }): {
14
15
  readonly col_widths: number[];