@gradio/dataframe 0.16.0 → 0.16.2

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.
@@ -176,15 +176,14 @@
176
176
  id: string;
177
177
  }[][] {
178
178
  const data_row_length = _values.length;
179
+ if (data_row_length === 0) return [];
179
180
  return Array(row_count[1] === "fixed" ? row_count[0] : data_row_length)
180
181
  .fill(0)
181
182
  .map((_, i) => {
182
183
  return Array(
183
184
  col_count[1] === "fixed"
184
185
  ? col_count[0]
185
- : data_row_length > 0
186
- ? _values[0].length
187
- : headers.length
186
+ : _values[0].length || headers.length
188
187
  )
189
188
  .fill(0)
190
189
  .map((_, j) => {
@@ -235,7 +234,7 @@
235
234
  dispatch("change", {
236
235
  data: data.map((row) => row.map((cell) => cell.value)),
237
236
  headers: _headers.map((h) => h.value),
238
- metadata: null
237
+ metadata: null // the metadata (display value, styling) cannot be changed by the user so we don't need to pass it up
239
238
  });
240
239
  if (!value_is_output) {
241
240
  dispatch("input");
@@ -473,12 +472,8 @@
473
472
  parent.focus();
474
473
 
475
474
  if (row_count[1] !== "dynamic") return;
476
- if (data.length === 0) {
477
- values = [Array(headers.length).fill("")];
478
- return;
479
- }
480
475
 
481
- const new_row = Array(data[0].length)
476
+ const new_row = Array(data[0]?.length || headers.length)
482
477
  .fill(0)
483
478
  .map((_, i) => {
484
479
  const _id = make_id();
@@ -486,7 +481,9 @@
486
481
  return { id: _id, value: "" };
487
482
  });
488
483
 
489
- if (index !== undefined && index >= 0 && index <= data.length) {
484
+ if (data.length === 0) {
485
+ data = [new_row];
486
+ } else if (index !== undefined && index >= 0 && index <= data.length) {
490
487
  data.splice(index, 0, new_row);
491
488
  } else {
492
489
  data.push(new_row);
@@ -786,15 +783,17 @@
786
783
  async function delete_col(index: number): Promise<void> {
787
784
  parent.focus();
788
785
  if (col_count[1] !== "dynamic") return;
789
- if (data[0].length <= 1) return;
786
+ if (_headers.length <= 1) return;
790
787
 
791
788
  _headers.splice(index, 1);
792
789
  _headers = _headers;
793
790
 
794
- data.forEach((row) => {
795
- row.splice(index, 1);
796
- });
797
- data = data;
791
+ if (data.length > 0) {
792
+ data.forEach((row) => {
793
+ row.splice(index, 1);
794
+ });
795
+ data = data;
796
+ }
798
797
  selected = false;
799
798
  }
800
799
 
@@ -902,24 +901,26 @@
902
901
  <svelte:window on:resize={() => set_cell_widths()} />
903
902
 
904
903
  <div class="table-container">
905
- <div class="header-row">
906
- {#if label && label.length !== 0 && show_label}
907
- <div class="label">
908
- <p>{label}</p>
909
- </div>
910
- {/if}
911
- <Toolbar
912
- {show_fullscreen_button}
913
- {is_fullscreen}
914
- on:click={toggle_fullscreen}
915
- on_copy={handle_copy}
916
- {show_copy_button}
917
- {show_search}
918
- on:search={(e) => handle_search(e.detail)}
919
- on_commit_filter={commit_filter}
920
- {current_search_query}
921
- />
922
- </div>
904
+ {#if (label && label.length !== 0 && show_label) || show_fullscreen_button || show_copy_button || show_search !== "none"}
905
+ <div class="header-row">
906
+ {#if label && label.length !== 0 && show_label}
907
+ <div class="label">
908
+ <p>{label}</p>
909
+ </div>
910
+ {/if}
911
+ <Toolbar
912
+ {show_fullscreen_button}
913
+ {is_fullscreen}
914
+ on:click={toggle_fullscreen}
915
+ on_copy={handle_copy}
916
+ {show_copy_button}
917
+ {show_search}
918
+ on:search={(e) => handle_search(e.detail)}
919
+ on_commit_filter={commit_filter}
920
+ {current_search_query}
921
+ />
922
+ </div>
923
+ {/if}
923
924
  <div
924
925
  bind:this={parent}
925
926
  class="table-wrap"
@@ -1064,184 +1065,205 @@
1064
1065
  bind:dragging
1065
1066
  aria_label={i18n("dataframe.drop_to_upload")}
1066
1067
  >
1067
- <VirtualTable
1068
- bind:items={data}
1069
- {max_height}
1070
- bind:actual_height={table_height}
1071
- bind:table_scrollbar_width={scrollbar_width}
1072
- selected={selected_index}
1073
- disable_scroll={active_cell_menu !== null ||
1074
- active_header_menu !== null}
1075
- >
1076
- {#if label && label.length !== 0}
1077
- <caption class="sr-only">{label}</caption>
1078
- {/if}
1079
- <tr slot="thead">
1080
- {#if show_row_numbers}
1081
- <th
1082
- class="row-number-header frozen-column always-frozen"
1083
- style="left: 0;"
1084
- >
1085
- <div class="cell-wrap">
1086
- <div class="header-content">
1087
- <div class="header-text"></div>
1088
- </div>
1089
- </div>
1090
- </th>
1068
+ <div class="table-wrap">
1069
+ <VirtualTable
1070
+ bind:items={data}
1071
+ {max_height}
1072
+ bind:actual_height={table_height}
1073
+ bind:table_scrollbar_width={scrollbar_width}
1074
+ selected={selected_index}
1075
+ disable_scroll={active_cell_menu !== null ||
1076
+ active_header_menu !== null}
1077
+ >
1078
+ {#if label && label.length !== 0}
1079
+ <caption class="sr-only">{label}</caption>
1091
1080
  {/if}
1092
- {#each _headers as { value, id }, i (id)}
1093
- <th
1094
- class:frozen-column={i < actual_pinned_columns}
1095
- class:last-frozen={i === actual_pinned_columns - 1}
1096
- class:focus={header_edit === i || selected_header === i}
1097
- aria-sort={get_sort_status(value, sort_by, sort_direction)}
1098
- style="width: {get_cell_width(i)}; left: {i <
1099
- actual_pinned_columns
1100
- ? i === 0
1101
- ? show_row_numbers
1102
- ? 'var(--cell-width-row-number)'
1103
- : '0'
1104
- : `calc(${show_row_numbers ? 'var(--cell-width-row-number) + ' : ''}${Array(
1105
- i
1106
- )
1107
- .fill(0)
1108
- .map((_, idx) => `var(--cell-width-${idx})`)
1109
- .join(' + ')})`
1110
- : 'auto'};"
1111
- on:click={() => {
1112
- toggle_header_button(i);
1113
- }}
1114
- >
1115
- <div class="cell-wrap">
1116
- <div class="header-content">
1081
+ <tr slot="thead">
1082
+ {#if show_row_numbers}
1083
+ <th
1084
+ class="row-number-header frozen-column always-frozen"
1085
+ style="left: 0;"
1086
+ >
1087
+ <div class="cell-wrap">
1088
+ <div class="header-content">
1089
+ <div class="header-text"></div>
1090
+ </div>
1091
+ </div>
1092
+ </th>
1093
+ {/if}
1094
+ {#each _headers as { value, id }, i (id)}
1095
+ <th
1096
+ class:frozen-column={i < actual_pinned_columns}
1097
+ class:last-frozen={i === actual_pinned_columns - 1}
1098
+ class:focus={header_edit === i || selected_header === i}
1099
+ aria-sort={get_sort_status(value, sort_by, sort_direction)}
1100
+ style="width: {get_cell_width(i)}; left: {i <
1101
+ actual_pinned_columns
1102
+ ? i === 0
1103
+ ? show_row_numbers
1104
+ ? 'var(--cell-width-row-number)'
1105
+ : '0'
1106
+ : `calc(${show_row_numbers ? 'var(--cell-width-row-number) + ' : ''}${Array(
1107
+ i
1108
+ )
1109
+ .fill(0)
1110
+ .map((_, idx) => `var(--cell-width-${idx})`)
1111
+ .join(' + ')})`
1112
+ : 'auto'};"
1113
+ on:click={() => {
1114
+ toggle_header_button(i);
1115
+ }}
1116
+ >
1117
+ <div class="cell-wrap">
1118
+ <div class="header-content">
1119
+ <EditableCell
1120
+ {max_chars}
1121
+ bind:value={_headers[i].value}
1122
+ bind:el={els[id].input}
1123
+ {latex_delimiters}
1124
+ {line_breaks}
1125
+ edit={header_edit === i}
1126
+ on:keydown={end_header_edit}
1127
+ on:dblclick={() => edit_header(i)}
1128
+ header
1129
+ {root}
1130
+ {editable}
1131
+ />
1132
+ <div class="sort-buttons">
1133
+ <SortIcon
1134
+ direction={sort_by === i ? sort_direction : null}
1135
+ on:sort={({ detail }) => handle_sort(i, detail)}
1136
+ {i18n}
1137
+ />
1138
+ </div>
1139
+ </div>
1140
+ {#if editable}
1141
+ <button
1142
+ class="cell-menu-button"
1143
+ on:click={(event) => toggle_header_menu(event, i)}
1144
+ on:touchstart={(event) => {
1145
+ event.preventDefault();
1146
+ const touch = event.touches[0];
1147
+ const mouseEvent = new MouseEvent("click", {
1148
+ clientX: touch.clientX,
1149
+ clientY: touch.clientY,
1150
+ bubbles: true,
1151
+ cancelable: true,
1152
+ view: window
1153
+ });
1154
+ toggle_header_menu(mouseEvent, i);
1155
+ }}
1156
+ >
1157
+ &#8942;
1158
+ </button>
1159
+ {/if}
1160
+ </div>
1161
+ </th>
1162
+ {/each}
1163
+ </tr>
1164
+ <tr slot="tbody" let:item let:index class:row_odd={index % 2 === 0}>
1165
+ {#if show_row_numbers}
1166
+ <td
1167
+ class="row-number frozen-column always-frozen"
1168
+ style="left: 0;"
1169
+ tabindex="-1"
1170
+ >
1171
+ {index + 1}
1172
+ </td>
1173
+ {/if}
1174
+ {#each item as { value, id }, j (id)}
1175
+ <td
1176
+ class:frozen-column={j < actual_pinned_columns}
1177
+ class:last-frozen={j === actual_pinned_columns - 1}
1178
+ tabindex={show_row_numbers && j === 0 ? -1 : 0}
1179
+ bind:this={els[id].cell}
1180
+ on:touchstart={(event) => {
1181
+ const touch = event.touches[0];
1182
+ const mouseEvent = new MouseEvent("click", {
1183
+ clientX: touch.clientX,
1184
+ clientY: touch.clientY,
1185
+ bubbles: true,
1186
+ cancelable: true,
1187
+ view: window
1188
+ });
1189
+ handle_cell_click(mouseEvent, index, j);
1190
+ }}
1191
+ on:mousedown={(event) => {
1192
+ event.preventDefault();
1193
+ event.stopPropagation();
1194
+ }}
1195
+ on:click={(event) => handle_cell_click(event, index, j)}
1196
+ style="width: {get_cell_width(j)}; left: {j <
1197
+ actual_pinned_columns
1198
+ ? j === 0
1199
+ ? show_row_numbers
1200
+ ? 'var(--cell-width-row-number)'
1201
+ : '0'
1202
+ : `calc(${show_row_numbers ? 'var(--cell-width-row-number) + ' : ''}${Array(
1203
+ j
1204
+ )
1205
+ .fill(0)
1206
+ .map((_, idx) => `var(--cell-width-${idx})`)
1207
+ .join(' + ')})`
1208
+ : 'auto'}; {styling?.[index]?.[j] || ''}"
1209
+ class:flash={copy_flash &&
1210
+ is_cell_selected([index, j], selected_cells)}
1211
+ class={is_cell_selected([index, j], selected_cells)}
1212
+ class:menu-active={active_cell_menu &&
1213
+ active_cell_menu.row === index &&
1214
+ active_cell_menu.col === j}
1215
+ >
1216
+ <div class="cell-wrap">
1117
1217
  <EditableCell
1118
- {max_chars}
1119
- bind:value={_headers[i].value}
1218
+ bind:value={data[index][j].value}
1120
1219
  bind:el={els[id].input}
1220
+ display_value={display_value?.[index]?.[j]}
1121
1221
  {latex_delimiters}
1122
1222
  {line_breaks}
1123
- edit={header_edit === i}
1124
- on:keydown={end_header_edit}
1125
- on:dblclick={() => edit_header(i)}
1126
- header
1127
- {root}
1128
1223
  {editable}
1224
+ edit={dequal(editing, [index, j])}
1225
+ datatype={Array.isArray(datatype) ? datatype[j] : datatype}
1226
+ on:blur={() => {
1227
+ clear_on_focus = false;
1228
+ parent.focus();
1229
+ }}
1230
+ on:focus={() => {
1231
+ const row = index;
1232
+ const col = j;
1233
+ if (
1234
+ !selected_cells.some(([r, c]) => r === row && c === col)
1235
+ ) {
1236
+ selected_cells = [[row, col]];
1237
+ }
1238
+ }}
1239
+ {clear_on_focus}
1240
+ {root}
1241
+ {max_chars}
1129
1242
  />
1130
- <div class="sort-buttons">
1131
- <SortIcon
1132
- direction={sort_by === i ? sort_direction : null}
1133
- on:sort={({ detail }) => handle_sort(i, detail)}
1134
- {i18n}
1135
- />
1136
- </div>
1243
+ {#if editable && should_show_cell_menu([index, j], selected_cells, editable)}
1244
+ <button
1245
+ class="cell-menu-button"
1246
+ on:click={(event) => toggle_cell_menu(event, index, j)}
1247
+ >
1248
+ &#8942;
1249
+ </button>
1250
+ {/if}
1137
1251
  </div>
1138
- {#if editable}
1139
- <button
1140
- class="cell-menu-button"
1141
- on:click={(event) => toggle_header_menu(event, i)}
1142
- >
1143
- &#8942;
1144
- </button>
1145
- {/if}
1146
- </div>
1147
- </th>
1148
- {/each}
1149
- </tr>
1150
- <tr slot="tbody" let:item let:index class:row_odd={index % 2 === 0}>
1151
- {#if show_row_numbers}
1152
- <td
1153
- class="row-number frozen-column always-frozen"
1154
- style="left: 0;"
1155
- tabindex="-1"
1156
- >
1157
- {index + 1}
1158
- </td>
1159
- {/if}
1160
- {#each item as { value, id }, j (id)}
1161
- <td
1162
- class:frozen-column={j < actual_pinned_columns}
1163
- class:last-frozen={j === actual_pinned_columns - 1}
1164
- tabindex={show_row_numbers && j === 0 ? -1 : 0}
1165
- bind:this={els[id].cell}
1166
- on:touchstart={(event) => {
1167
- const touch = event.touches[0];
1168
- const mouseEvent = new MouseEvent("click", {
1169
- clientX: touch.clientX,
1170
- clientY: touch.clientY,
1171
- bubbles: true,
1172
- cancelable: true,
1173
- view: window
1174
- });
1175
- handle_cell_click(mouseEvent, index, j);
1176
- }}
1177
- on:mousedown={(event) => {
1178
- event.preventDefault();
1179
- event.stopPropagation();
1180
- }}
1181
- on:click={(event) => handle_cell_click(event, index, j)}
1182
- style="width: {get_cell_width(j)}; left: {j <
1183
- actual_pinned_columns
1184
- ? j === 0
1185
- ? show_row_numbers
1186
- ? 'var(--cell-width-row-number)'
1187
- : '0'
1188
- : `calc(${show_row_numbers ? 'var(--cell-width-row-number) + ' : ''}${Array(
1189
- j
1190
- )
1191
- .fill(0)
1192
- .map((_, idx) => `var(--cell-width-${idx})`)
1193
- .join(' + ')})`
1194
- : 'auto'}; {styling?.[index]?.[j] || ''}"
1195
- class:flash={copy_flash &&
1196
- is_cell_selected([index, j], selected_cells)}
1197
- class={is_cell_selected([index, j], selected_cells)}
1198
- class:menu-active={active_cell_menu &&
1199
- active_cell_menu.row === index &&
1200
- active_cell_menu.col === j}
1201
- >
1202
- <div class="cell-wrap">
1203
- <EditableCell
1204
- bind:value={data[index][j].value}
1205
- bind:el={els[id].input}
1206
- display_value={display_value?.[index]?.[j]}
1207
- {latex_delimiters}
1208
- {line_breaks}
1209
- {editable}
1210
- edit={dequal(editing, [index, j])}
1211
- datatype={Array.isArray(datatype) ? datatype[j] : datatype}
1212
- on:blur={() => {
1213
- clear_on_focus = false;
1214
- parent.focus();
1215
- }}
1216
- on:focus={() => {
1217
- const row = index;
1218
- const col = j;
1219
- if (
1220
- !selected_cells.some(([r, c]) => r === row && c === col)
1221
- ) {
1222
- selected_cells = [[row, col]];
1223
- }
1224
- }}
1225
- {clear_on_focus}
1226
- {root}
1227
- {max_chars}
1228
- />
1229
- {#if editable && should_show_cell_menu([index, j], selected_cells, editable)}
1230
- <button
1231
- class="cell-menu-button"
1232
- on:click={(event) => toggle_cell_menu(event, index, j)}
1233
- >
1234
- &#8942;
1235
- </button>
1236
- {/if}
1237
- </div>
1238
- </td>
1239
- {/each}
1240
- </tr>
1241
- </VirtualTable>
1252
+ </td>
1253
+ {/each}
1254
+ </tr>
1255
+ </VirtualTable>
1256
+ </div>
1242
1257
  </Upload>
1243
1258
  </div>
1244
1259
  </div>
1260
+ {#if data.length === 0 && editable && row_count[1] === "dynamic"}
1261
+ <div class="add-row-container">
1262
+ <button class="add-row-button" on:click={() => add_row()}>
1263
+ <span>+</span>
1264
+ </button>
1265
+ </div>
1266
+ {/if}
1245
1267
 
1246
1268
  {#if active_cell_menu}
1247
1269
  <CellMenu
@@ -1278,7 +1300,7 @@
1278
1300
  on_delete_row={() => delete_row_at(active_cell_menu?.row ?? -1)}
1279
1301
  on_delete_col={() => delete_col_at(active_header_menu?.col ?? -1)}
1280
1302
  can_delete_rows={false}
1281
- can_delete_cols={data[0].length > 1}
1303
+ can_delete_cols={_headers.length > 1}
1282
1304
  />
1283
1305
  {/if}
1284
1306
 
@@ -1300,8 +1322,6 @@
1300
1322
  .table-wrap {
1301
1323
  position: relative;
1302
1324
  transition: 150ms;
1303
- border: 1px solid var(--border-color-primary);
1304
- border-radius: var(--table-radius);
1305
1325
  }
1306
1326
 
1307
1327
  .table-wrap.menu-open {
@@ -1334,6 +1354,12 @@
1334
1354
  border-collapse: separate;
1335
1355
  }
1336
1356
 
1357
+ .table-wrap > :global(button) {
1358
+ border: 1px solid var(--border-color-primary);
1359
+ border-radius: var(--table-radius);
1360
+ overflow: hidden;
1361
+ }
1362
+
1337
1363
  div:not(.no-wrap) td {
1338
1364
  overflow-wrap: anywhere;
1339
1365
  }
@@ -1376,10 +1402,12 @@
1376
1402
 
1377
1403
  th:first-child {
1378
1404
  border-top-left-radius: var(--table-radius);
1405
+ border-bottom-left-radius: var(--table-radius);
1379
1406
  }
1380
1407
 
1381
1408
  th:last-child {
1382
1409
  border-top-right-radius: var(--table-radius);
1410
+ border-bottom-right-radius: var(--table-radius);
1383
1411
  }
1384
1412
 
1385
1413
  th.focus,
@@ -1409,6 +1437,7 @@
1409
1437
  display: flex;
1410
1438
  align-items: center;
1411
1439
  flex-shrink: 0;
1440
+ order: -1;
1412
1441
  }
1413
1442
 
1414
1443
  .editing {
@@ -1417,17 +1446,24 @@
1417
1446
 
1418
1447
  .cell-wrap {
1419
1448
  display: flex;
1420
- align-items: flex-start;
1449
+ align-items: center;
1450
+ justify-content: flex-start;
1421
1451
  outline: none;
1422
1452
  min-height: var(--size-9);
1423
1453
  position: relative;
1424
- height: auto;
1454
+ height: 100%;
1455
+ padding: var(--size-2);
1456
+ box-sizing: border-box;
1457
+ margin: 0;
1458
+ gap: var(--size-1);
1459
+ overflow: visible;
1460
+ min-width: 0;
1461
+ border-radius: var(--table-radius);
1425
1462
  }
1426
1463
 
1427
1464
  .header-content {
1428
1465
  display: flex;
1429
1466
  align-items: center;
1430
- justify-content: space-between;
1431
1467
  overflow: hidden;
1432
1468
  flex-grow: 1;
1433
1469
  min-width: 0;
@@ -1435,7 +1471,6 @@
1435
1471
  overflow-wrap: break-word;
1436
1472
  word-break: normal;
1437
1473
  height: 100%;
1438
- padding: var(--size-1);
1439
1474
  gap: var(--size-1);
1440
1475
  }
1441
1476
 
@@ -1465,7 +1500,8 @@
1465
1500
  transform: translateY(-50%);
1466
1501
  }
1467
1502
 
1468
- .cell-selected .cell-menu-button {
1503
+ .cell-selected .cell-menu-button,
1504
+ th:hover .cell-menu-button {
1469
1505
  display: flex;
1470
1506
  align-items: center;
1471
1507
  justify-content: center;
@@ -1682,4 +1718,24 @@
1682
1718
  .always-frozen {
1683
1719
  z-index: var(--layer-3);
1684
1720
  }
1721
+
1722
+ .add-row-container {
1723
+ margin-top: var(--size-2);
1724
+ }
1725
+
1726
+ .add-row-button {
1727
+ width: 100%;
1728
+ padding: var(--size-1);
1729
+ background: transparent;
1730
+ border: 1px dashed var(--border-color-primary);
1731
+ border-radius: var(--radius-sm);
1732
+ color: var(--body-text-color);
1733
+ cursor: pointer;
1734
+ transition: all 150ms;
1735
+ }
1736
+
1737
+ .add-row-button:hover {
1738
+ background: var(--background-fill-secondary);
1739
+ border-style: solid;
1740
+ }
1685
1741
  </style>