@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.
@@ -1,5 +1,4 @@
1
1
  <script>import { afterUpdate, createEventDispatcher, tick, onMount } from "svelte";
2
- import { dsvFormat } from "d3-dsv";
3
2
  import { dequal } from "dequal/lite";
4
3
  import { Upload } from "@gradio/upload";
5
4
  import EditableCell from "./EditableCell.svelte";
@@ -7,6 +6,18 @@ import {} from "@gradio/client";
7
6
  import VirtualTable from "./VirtualTable.svelte";
8
7
  import CellMenu from "./CellMenu.svelte";
9
8
  import Toolbar from "./Toolbar.svelte";
9
+ import {
10
+ is_cell_selected,
11
+ handle_selection,
12
+ handle_delete_key,
13
+ should_show_cell_menu,
14
+ get_next_cell_coordinates,
15
+ get_range_selection,
16
+ move_cursor,
17
+ get_current_indices,
18
+ handle_click_outside as handle_click_outside_util
19
+ } from "./selection_utils";
20
+ import { copy_table_data, get_max, handle_file_upload } from "./table_utils";
10
21
  export let datatype;
11
22
  export let label = null;
12
23
  export let show_label = true;
@@ -26,31 +37,30 @@ export let show_row_numbers = false;
26
37
  export let upload;
27
38
  export let stream_handler;
28
39
  export let show_fullscreen_button = false;
40
+ export let show_copy_button = false;
29
41
  export let value_is_output = false;
42
+ export let max_chars = void 0;
43
+ let selected_cells = [];
44
+ $:
45
+ selected_cells = [...selected_cells];
30
46
  let selected = false;
31
- let clicked_cell = void 0;
47
+ $:
48
+ selected = selected_cells.length > 0 ? selected_cells[selected_cells.length - 1] : false;
32
49
  export let display_value = null;
33
50
  export let styling = null;
34
51
  let t_rect;
52
+ let els = {};
53
+ let data_binding = {};
35
54
  const dispatch = createEventDispatcher();
36
55
  let editing = false;
56
+ let clear_on_focus = false;
57
+ let header_edit = false;
58
+ let selected_header = false;
59
+ let active_cell_menu = null;
60
+ let active_header_menu = null;
61
+ let is_fullscreen = false;
62
+ let dragging = false;
37
63
  const get_data_at = (row, col) => data?.[row]?.[col]?.value;
38
- let last_selected = null;
39
- $: {
40
- if (selected !== false && !dequal(selected, last_selected)) {
41
- const [row, col] = selected;
42
- if (!isNaN(row) && !isNaN(col) && data[row]) {
43
- dispatch("select", {
44
- index: [row, col],
45
- value: get_data_at(row, col),
46
- row_value: data[row].map((d) => d.value)
47
- });
48
- last_selected = selected;
49
- }
50
- }
51
- }
52
- let els = {};
53
- let data_binding = {};
54
64
  function make_id() {
55
65
  return Math.random().toString(36).substring(2, 15);
56
66
  }
@@ -75,9 +85,7 @@ function make_headers(_head) {
75
85
  }
76
86
  function process_data(_values) {
77
87
  const data_row_length = _values.length;
78
- return Array(
79
- row_count[1] === "fixed" ? row_count[0] : data_row_length < row_count[0] ? row_count[0] : data_row_length
80
- ).fill(0).map(
88
+ return Array(row_count[1] === "fixed" ? row_count[0] : data_row_length).fill(0).map(
81
89
  (_, i) => Array(
82
90
  col_count[1] === "fixed" ? col_count[0] : data_row_length > 0 ? _values[0].length : headers.length
83
91
  ).fill(0).map((_2, j) => {
@@ -135,36 +143,6 @@ function get_sort_status(name, _sort, direction) {
135
143
  }
136
144
  return "none";
137
145
  }
138
- function get_current_indices(id) {
139
- return data.reduce(
140
- (acc, arr, i) => {
141
- const j = arr.reduce(
142
- (_acc, _data, k) => id === _data.id ? k : _acc,
143
- -1
144
- );
145
- return j === -1 ? acc : [i, j];
146
- },
147
- [-1, -1]
148
- );
149
- }
150
- function move_cursor(key, current_coords) {
151
- const dir = {
152
- ArrowRight: [0, 1],
153
- ArrowLeft: [0, -1],
154
- ArrowDown: [1, 0],
155
- ArrowUp: [-1, 0]
156
- }[key];
157
- const i = current_coords[0] + dir[0];
158
- const j = current_coords[1] + dir[1];
159
- if (i < 0 && j <= 0) {
160
- selected_header = j;
161
- selected = false;
162
- } else {
163
- const is_data = data[i]?.[j];
164
- selected = is_data ? [i, j] : selected;
165
- }
166
- }
167
- let clear_on_focus = false;
168
146
  async function handle_keydown(event) {
169
147
  if (selected_header !== false && header_edit === false) {
170
148
  switch (event.key) {
@@ -187,6 +165,43 @@ async function handle_keydown(event) {
187
165
  break;
188
166
  }
189
167
  }
168
+ if (event.key === "Delete" || event.key === "Backspace") {
169
+ if (!editable)
170
+ return;
171
+ if (editing) {
172
+ const [row, col] = editing;
173
+ const input_el = els[data[row][col].id].input;
174
+ if (input_el && input_el.selectionStart !== input_el.selectionEnd) {
175
+ return;
176
+ }
177
+ if (event.key === "Delete" && input_el?.selectionStart !== input_el?.value.length) {
178
+ return;
179
+ }
180
+ if (event.key === "Backspace" && input_el?.selectionStart !== 0) {
181
+ return;
182
+ }
183
+ }
184
+ event.preventDefault();
185
+ if (selected_cells.length > 0) {
186
+ data = handle_delete_key(data, selected_cells);
187
+ dispatch("change", {
188
+ data: data.map((row) => row.map((cell) => cell.value)),
189
+ headers: _headers.map((h) => h.value),
190
+ metadata: null
191
+ });
192
+ if (!value_is_output) {
193
+ dispatch("input");
194
+ }
195
+ }
196
+ return;
197
+ }
198
+ if (event.key === "c" && (event.metaKey || event.ctrlKey)) {
199
+ event.preventDefault();
200
+ if (selected_cells.length > 0) {
201
+ await handle_copy();
202
+ }
203
+ return;
204
+ }
190
205
  if (!selected) {
191
206
  return;
192
207
  }
@@ -199,7 +214,25 @@ async function handle_keydown(event) {
199
214
  if (editing)
200
215
  break;
201
216
  event.preventDefault();
202
- move_cursor(event.key, [i, j]);
217
+ const next_coords = move_cursor(event.key, [i, j], data);
218
+ if (next_coords) {
219
+ if (event.shiftKey) {
220
+ selected_cells = get_range_selection(
221
+ selected_cells.length > 0 ? selected_cells[0] : [i, j],
222
+ next_coords
223
+ );
224
+ editing = false;
225
+ } else {
226
+ selected_cells = [next_coords];
227
+ editing = next_coords;
228
+ clear_on_focus = false;
229
+ }
230
+ selected = next_coords;
231
+ } else if (next_coords === false && event.key === "ArrowUp" && i === 0) {
232
+ selected_header = j;
233
+ selected = false;
234
+ editing = false;
235
+ }
203
236
  break;
204
237
  case "Escape":
205
238
  if (!editable)
@@ -227,34 +260,26 @@ async function handle_keydown(event) {
227
260
  selected = [i, j];
228
261
  } else {
229
262
  editing = [i, j];
263
+ clear_on_focus = false;
230
264
  }
231
265
  }
232
266
  break;
233
- case "Backspace":
234
- if (!editable)
235
- break;
236
- if (!editing) {
237
- event.preventDefault();
238
- data[i][j].value = "";
239
- }
240
- break;
241
- case "Delete":
242
- if (!editable)
243
- break;
244
- if (!editing) {
245
- event.preventDefault();
246
- data[i][j].value = "";
247
- }
248
- break;
249
267
  case "Tab":
250
- let direction = event.shiftKey ? -1 : 1;
251
- let is_data_x = data[i][j + direction];
252
- let is_data_y = data?.[i + direction]?.[direction > 0 ? 0 : _headers.length - 1];
253
- if (is_data_x || is_data_y) {
254
- event.preventDefault();
255
- selected = is_data_x ? [i, j + direction] : [i + direction, direction > 0 ? 0 : _headers.length - 1];
256
- }
268
+ event.preventDefault();
257
269
  editing = false;
270
+ const next_cell = get_next_cell_coordinates(
271
+ [i, j],
272
+ data,
273
+ event.shiftKey
274
+ );
275
+ if (next_cell) {
276
+ selected_cells = [next_cell];
277
+ selected = next_cell;
278
+ if (editable) {
279
+ editing = next_cell;
280
+ clear_on_focus = false;
281
+ }
282
+ }
258
283
  break;
259
284
  default:
260
285
  if (!editable)
@@ -279,16 +304,13 @@ function handle_sort(col) {
279
304
  }
280
305
  }
281
306
  }
282
- let header_edit;
283
- let select_on_focus = false;
284
- let selected_header = false;
285
307
  async function edit_header(i, _select = false) {
286
308
  if (!editable || col_count[1] !== "dynamic" || header_edit === i)
287
309
  return;
288
310
  selected = false;
311
+ selected_cells = [];
289
312
  selected_header = i;
290
313
  header_edit = i;
291
- select_on_focus = _select;
292
314
  }
293
315
  function end_header_edit(event) {
294
316
  if (!editable)
@@ -349,78 +371,14 @@ async function add_col(index) {
349
371
  });
350
372
  }
351
373
  function handle_click_outside(event) {
352
- if (active_cell_menu && !event.target.closest(".cell-menu") || active_header_menu && !event.target.closest(".cell-menu")) {
374
+ if (handle_click_outside_util(event, parent)) {
375
+ editing = false;
376
+ selected_cells = [];
377
+ header_edit = false;
378
+ selected_header = false;
353
379
  active_cell_menu = null;
354
380
  active_header_menu = null;
355
381
  }
356
- const [trigger] = event.composedPath();
357
- if (parent.contains(trigger)) {
358
- return;
359
- }
360
- clicked_cell = void 0;
361
- editing = false;
362
- selected = false;
363
- header_edit = false;
364
- selected_header = false;
365
- active_cell_menu = null;
366
- active_header_menu = null;
367
- }
368
- function guess_delimitaor(text, possibleDelimiters) {
369
- return possibleDelimiters.filter(weedOut);
370
- function weedOut(delimiter) {
371
- var cache = -1;
372
- return text.split("\n").every(checkLength);
373
- function checkLength(line) {
374
- if (!line) {
375
- return true;
376
- }
377
- var length = line.split(delimiter).length;
378
- if (cache < 0) {
379
- cache = length;
380
- }
381
- return cache === length && length > 1;
382
- }
383
- }
384
- }
385
- function data_uri_to_blob(data_uri) {
386
- const byte_str = atob(data_uri.split(",")[1]);
387
- const mime_str = data_uri.split(",")[0].split(":")[1].split(";")[0];
388
- const ab = new ArrayBuffer(byte_str.length);
389
- const ia = new Uint8Array(ab);
390
- for (let i = 0; i < byte_str.length; i++) {
391
- ia[i] = byte_str.charCodeAt(i);
392
- }
393
- return new Blob([ab], { type: mime_str });
394
- }
395
- function blob_to_string(blob) {
396
- const reader = new FileReader();
397
- function handle_read(e) {
398
- if (!e?.target?.result || typeof e.target.result !== "string")
399
- return;
400
- const [delimiter] = guess_delimitaor(e.target.result, [",", " "]);
401
- const [head, ...rest] = dsvFormat(delimiter).parseRows(e.target.result);
402
- _headers = make_headers(
403
- col_count[1] === "fixed" ? head.slice(0, col_count[0]) : head
404
- );
405
- values = rest;
406
- reader.removeEventListener("loadend", handle_read);
407
- }
408
- reader.addEventListener("loadend", handle_read);
409
- reader.readAsText(blob);
410
- }
411
- let dragging = false;
412
- function get_max(_d) {
413
- if (!_d || _d.length === 0 || !_d[0])
414
- return [];
415
- let max2 = _d[0].slice();
416
- for (let i = 0; i < _d.length; i++) {
417
- for (let j = 0; j < _d[i].length; j++) {
418
- if (`${max2[j].value}`.length < `${_d[i][j].value}`.length) {
419
- max2[j] = _d[i][j];
420
- }
421
- }
422
- }
423
- return max2;
424
382
  }
425
383
  $:
426
384
  max = get_max(data);
@@ -476,7 +434,7 @@ function sort_data(_data, _display_value, _styling, col, dir) {
476
434
  });
477
435
  data = data;
478
436
  if (id) {
479
- const [i, j] = get_current_indices(id);
437
+ const [i, j] = get_current_indices(id, data);
480
438
  selected = [i, j];
481
439
  }
482
440
  }
@@ -486,7 +444,7 @@ $:
486
444
  selected_index = !!selected && selected[0];
487
445
  let is_visible = false;
488
446
  onMount(() => {
489
- const observer = new IntersectionObserver((entries, observer2) => {
447
+ const observer = new IntersectionObserver((entries) => {
490
448
  entries.forEach((entry) => {
491
449
  if (entry.isIntersecting && !is_visible) {
492
450
  set_cell_widths();
@@ -509,8 +467,34 @@ onMount(() => {
509
467
  );
510
468
  };
511
469
  });
512
- let highlighted_column = null;
513
- let active_cell_menu = null;
470
+ function handle_cell_click(event, row, col) {
471
+ event.preventDefault();
472
+ event.stopPropagation();
473
+ clear_on_focus = false;
474
+ active_cell_menu = null;
475
+ active_header_menu = null;
476
+ selected_header = false;
477
+ header_edit = false;
478
+ selected_cells = handle_selection([row, col], selected_cells, event);
479
+ if (selected_cells.length === 1 && editable) {
480
+ editing = [row, col];
481
+ tick().then(() => {
482
+ const input_el = els[data[row][col].id].input;
483
+ if (input_el) {
484
+ input_el.focus();
485
+ input_el.selectionStart = input_el.selectionEnd = input_el.value.length;
486
+ }
487
+ });
488
+ } else {
489
+ editing = false;
490
+ }
491
+ toggle_cell_button(row, col);
492
+ dispatch("select", {
493
+ index: [row, col],
494
+ value: get_data_at(row, col),
495
+ row_value: data[row].map((d) => d.value)
496
+ });
497
+ }
514
498
  function toggle_cell_menu(event, row, col) {
515
499
  event.stopPropagation();
516
500
  if (active_cell_menu && active_cell_menu.row === row && active_cell_menu.col === col) {
@@ -519,12 +503,7 @@ function toggle_cell_menu(event, row, col) {
519
503
  const cell = event.target.closest("td");
520
504
  if (cell) {
521
505
  const rect = cell.getBoundingClientRect();
522
- active_cell_menu = {
523
- row,
524
- col,
525
- x: rect.right,
526
- y: rect.bottom
527
- };
506
+ active_cell_menu = { row, col, x: rect.right, y: rect.bottom };
528
507
  }
529
508
  }
530
509
  }
@@ -547,21 +526,11 @@ function handle_resize() {
547
526
  }
548
527
  let active_button = null;
549
528
  function toggle_header_button(col) {
550
- if (active_button?.type === "header" && active_button.col === col) {
551
- active_button = null;
552
- } else {
553
- active_button = { type: "header", col };
554
- }
529
+ active_button = active_button?.type === "header" && active_button.col === col ? null : { type: "header", col };
555
530
  }
556
531
  function toggle_cell_button(row, col) {
557
- if (active_button?.type === "cell" && active_button.row === row && active_button.col === col) {
558
- active_button = null;
559
- } else {
560
- active_button = { type: "cell", row, col };
561
- }
532
+ active_button = active_button?.type === "cell" && active_button.row === row && active_button.col === col ? null : { type: "cell", row, col };
562
533
  }
563
- let active_header_menu = null;
564
- let is_fullscreen = false;
565
534
  function toggle_fullscreen() {
566
535
  if (!document.fullscreenElement) {
567
536
  parent.requestFullscreen();
@@ -574,6 +543,9 @@ function toggle_fullscreen() {
574
543
  function handle_fullscreen_change() {
575
544
  is_fullscreen = !!document.fullscreenElement;
576
545
  }
546
+ async function handle_copy() {
547
+ await copy_table_data(data, _headers, selected_cells);
548
+ }
577
549
  function toggle_header_menu(event, col) {
578
550
  event.stopPropagation();
579
551
  if (active_header_menu && active_header_menu.col === col) {
@@ -582,17 +554,47 @@ function toggle_header_menu(event, col) {
582
554
  const header = event.target.closest("th");
583
555
  if (header) {
584
556
  const rect = header.getBoundingClientRect();
585
- active_header_menu = {
586
- col,
587
- x: rect.right,
588
- y: rect.bottom
589
- };
557
+ active_header_menu = { col, x: rect.right, y: rect.bottom };
590
558
  }
591
559
  }
592
560
  }
593
561
  afterUpdate(() => {
594
562
  value_is_output = false;
595
563
  });
564
+ async function delete_row(index) {
565
+ parent.focus();
566
+ if (row_count[1] !== "dynamic")
567
+ return;
568
+ if (data.length <= 1)
569
+ return;
570
+ data.splice(index, 1);
571
+ data = data;
572
+ selected = false;
573
+ }
574
+ async function delete_col(index) {
575
+ parent.focus();
576
+ if (col_count[1] !== "dynamic")
577
+ return;
578
+ if (data[0].length <= 1)
579
+ return;
580
+ _headers.splice(index, 1);
581
+ _headers = _headers;
582
+ data.forEach((row) => {
583
+ row.splice(index, 1);
584
+ });
585
+ data = data;
586
+ selected = false;
587
+ }
588
+ function delete_row_at(index) {
589
+ delete_row(index);
590
+ active_cell_menu = null;
591
+ active_header_menu = null;
592
+ }
593
+ function delete_col_at(index) {
594
+ delete_col(index);
595
+ active_cell_menu = null;
596
+ active_header_menu = null;
597
+ }
596
598
  </script>
597
599
 
598
600
  <svelte:window on:resize={() => set_cell_widths()} />
@@ -608,6 +610,8 @@ afterUpdate(() => {
608
610
  {show_fullscreen_button}
609
611
  {is_fullscreen}
610
612
  on:click={toggle_fullscreen}
613
+ on_copy={handle_copy}
614
+ {show_copy_button}
611
615
  />
612
616
  </div>
613
617
  <div
@@ -648,6 +652,7 @@ afterUpdate(() => {
648
652
  edit={false}
649
653
  el={null}
650
654
  {root}
655
+ {editable}
651
656
  />
652
657
 
653
658
  <div
@@ -683,6 +688,7 @@ afterUpdate(() => {
683
688
  edit={false}
684
689
  el={null}
685
690
  {root}
691
+ {editable}
686
692
  />
687
693
  </div>
688
694
  </td>
@@ -698,8 +704,20 @@ afterUpdate(() => {
698
704
  boundedheight={false}
699
705
  disable_click={true}
700
706
  {root}
701
- on:load={(e) => blob_to_string(data_uri_to_blob(e.detail.data))}
707
+ on:load={({ detail }) =>
708
+ handle_file_upload(
709
+ detail.data,
710
+ col_count,
711
+ (head) => {
712
+ _headers = make_headers(head);
713
+ return _headers;
714
+ },
715
+ (vals) => {
716
+ values = vals;
717
+ }
718
+ )}
702
719
  bind:dragging
720
+ aria_label={i18n("dataframe.drop_to_upload")}
703
721
  >
704
722
  <VirtualTable
705
723
  bind:items={data}
@@ -727,6 +745,7 @@ afterUpdate(() => {
727
745
  <div class="cell-wrap">
728
746
  <div class="header-content">
729
747
  <EditableCell
748
+ {max_chars}
730
749
  bind:value={_headers[i].value}
731
750
  bind:el={els[id].input}
732
751
  {latex_delimiters}
@@ -736,6 +755,7 @@ afterUpdate(() => {
736
755
  on:dblclick={() => edit_header(i)}
737
756
  header
738
757
  {root}
758
+ {editable}
739
759
  />
740
760
  <button
741
761
  class:sorted={sort_by === i}
@@ -764,7 +784,7 @@ afterUpdate(() => {
764
784
  class="cell-menu-button"
765
785
  on:click={(event) => toggle_header_menu(event, i)}
766
786
  >
767
-
787
+ &#8942;
768
788
  </button>
769
789
  {/if}
770
790
  </div>
@@ -780,40 +800,24 @@ afterUpdate(() => {
780
800
  <td
781
801
  tabindex="0"
782
802
  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);
803
+ const touch = event.touches[0];
804
+ const mouseEvent = new MouseEvent("click", {
805
+ clientX: touch.clientX,
806
+ clientY: touch.clientY,
807
+ bubbles: true,
808
+ cancelable: true,
809
+ view: window
810
+ });
811
+ handle_cell_click(mouseEvent, index, j);
794
812
  }}
795
813
  on:mousedown={(event) => {
796
814
  event.preventDefault();
797
815
  event.stopPropagation();
798
816
  }}
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
- }
812
- toggle_cell_button(index, j);
813
- }}
817
+ on:click={(event) => handle_cell_click(event, index, j)}
814
818
  style:width="var(--cell-width-{j})"
815
819
  style={styling?.[index]?.[j] || ""}
816
- class:focus={dequal(selected, [index, j])}
820
+ class={is_cell_selected([index, j], selected_cells)}
817
821
  class:menu-active={active_cell_menu &&
818
822
  active_cell_menu.row === index &&
819
823
  active_cell_menu.col === j}
@@ -832,15 +836,25 @@ afterUpdate(() => {
832
836
  clear_on_focus = false;
833
837
  parent.focus();
834
838
  }}
839
+ on:focus={() => {
840
+ const row = index;
841
+ const col = j;
842
+ if (
843
+ !selected_cells.some(([r, c]) => r === row && c === col)
844
+ ) {
845
+ selected_cells = [[row, col]];
846
+ }
847
+ }}
835
848
  {clear_on_focus}
836
849
  {root}
850
+ {max_chars}
837
851
  />
838
- {#if editable}
852
+ {#if editable && should_show_cell_menu([index, j], selected_cells, editable)}
839
853
  <button
840
854
  class="cell-menu-button"
841
855
  on:click={(event) => toggle_cell_menu(event, index, j)}
842
856
  >
843
-
857
+ &#8942;
844
858
  </button>
845
859
  {/if}
846
860
  </div>
@@ -852,18 +866,22 @@ afterUpdate(() => {
852
866
  </div>
853
867
  </div>
854
868
 
855
- {#if active_cell_menu !== null}
869
+ {#if active_cell_menu}
856
870
  <CellMenu
857
- {i18n}
858
871
  x={active_cell_menu.x}
859
872
  y={active_cell_menu.y}
860
- row={active_cell_menu?.row ?? -1}
873
+ row={active_cell_menu.row}
861
874
  {col_count}
862
875
  {row_count}
863
- on_add_row_above={() => add_row_at(active_cell_menu?.row ?? -1, "above")}
864
- on_add_row_below={() => add_row_at(active_cell_menu?.row ?? -1, "below")}
865
- on_add_column_left={() => add_col_at(active_cell_menu?.col ?? -1, "left")}
866
- on_add_column_right={() => add_col_at(active_cell_menu?.col ?? -1, "right")}
876
+ on_add_row_above={() => add_row_at(active_cell_menu?.row || 0, "above")}
877
+ on_add_row_below={() => add_row_at(active_cell_menu?.row || 0, "below")}
878
+ on_add_column_left={() => add_col_at(active_cell_menu?.col || 0, "left")}
879
+ on_add_column_right={() => add_col_at(active_cell_menu?.col || 0, "right")}
880
+ on_delete_row={() => delete_row_at(active_cell_menu?.row || 0)}
881
+ on_delete_col={() => delete_col_at(active_cell_menu?.col || 0)}
882
+ can_delete_rows={data.length > 1}
883
+ can_delete_cols={data[0].length > 1}
884
+ {i18n}
867
885
  />
868
886
  {/if}
869
887
 
@@ -880,25 +898,18 @@ afterUpdate(() => {
880
898
  on_add_column_left={() => add_col_at(active_header_menu?.col ?? -1, "left")}
881
899
  on_add_column_right={() =>
882
900
  add_col_at(active_header_menu?.col ?? -1, "right")}
901
+ on_delete_row={() => delete_row_at(active_cell_menu?.row ?? -1)}
902
+ on_delete_col={() => delete_col_at(active_header_menu?.col ?? -1)}
903
+ can_delete_rows={false}
904
+ can_delete_cols={data[0].length > 1}
883
905
  />
884
906
  {/if}
885
907
 
886
908
  <style>
887
- .button-wrap:hover svg {
888
- color: var(--color-accent);
889
- }
890
-
891
- .button-wrap svg {
892
- margin-right: var(--size-1);
893
- margin-left: -5px;
894
- }
895
-
896
- .label p {
897
- position: relative;
898
- z-index: var(--layer-4);
899
- margin-bottom: var(--size-2);
900
- color: var(--block-label-text-color);
901
- font-size: var(--block-label-text-size);
909
+ .table-container {
910
+ display: flex;
911
+ flex-direction: column;
912
+ gap: var(--size-2);
902
913
  }
903
914
 
904
915
  .table-wrap {
@@ -911,7 +922,6 @@ afterUpdate(() => {
911
922
 
912
923
  .table-wrap:focus-within {
913
924
  outline: none;
914
- background-color: none;
915
925
  }
916
926
 
917
927
  .dragging {
@@ -933,6 +943,7 @@ afterUpdate(() => {
933
943
  line-height: var(--line-md);
934
944
  font-family: var(--font-mono);
935
945
  border-spacing: 0;
946
+ border-collapse: separate;
936
947
  }
937
948
 
938
949
  div:not(.no-wrap) td {
@@ -987,6 +998,12 @@ afterUpdate(() => {
987
998
  th.focus,
988
999
  td.focus {
989
1000
  --ring-color: var(--color-accent);
1001
+ box-shadow: inset 0 0 0 2px var(--ring-color);
1002
+ z-index: var(--layer-1);
1003
+ }
1004
+
1005
+ th.focus {
1006
+ z-index: var(--layer-2);
990
1007
  }
991
1008
 
992
1009
  tr:last-child td:first-child {
@@ -1015,7 +1032,6 @@ afterUpdate(() => {
1015
1032
  cursor: pointer;
1016
1033
  padding: var(--size-2);
1017
1034
  color: var(--body-text-color-subdued);
1018
- line-height: var(--text-sm);
1019
1035
  }
1020
1036
 
1021
1037
  .sort-button:hover {
@@ -1036,11 +1052,11 @@ afterUpdate(() => {
1036
1052
 
1037
1053
  .cell-wrap {
1038
1054
  display: flex;
1039
- align-items: center;
1055
+ align-items: flex-start;
1040
1056
  outline: none;
1041
- height: var(--size-full);
1042
1057
  min-height: var(--size-9);
1043
- overflow: hidden;
1058
+ position: relative;
1059
+ height: auto;
1044
1060
  }
1045
1061
 
1046
1062
  .header-content {
@@ -1049,12 +1065,9 @@ afterUpdate(() => {
1049
1065
  overflow: hidden;
1050
1066
  flex-grow: 1;
1051
1067
  min-width: 0;
1052
- }
1053
-
1054
- .controls-wrap {
1055
- display: flex;
1056
- justify-content: flex-end;
1057
- padding-top: var(--size-2);
1068
+ white-space: normal;
1069
+ overflow-wrap: break-word;
1070
+ word-break: break-word;
1058
1071
  }
1059
1072
 
1060
1073
  .row_odd {
@@ -1065,10 +1078,6 @@ afterUpdate(() => {
1065
1078
  background: var(--background-fill-primary);
1066
1079
  }
1067
1080
 
1068
- table {
1069
- border-collapse: separate;
1070
- }
1071
-
1072
1081
  .cell-menu-button {
1073
1082
  flex-shrink: 0;
1074
1083
  display: none;
@@ -1081,28 +1090,35 @@ afterUpdate(() => {
1081
1090
  padding: 0;
1082
1091
  margin-right: var(--spacing-sm);
1083
1092
  z-index: var(--layer-1);
1093
+ position: absolute;
1094
+ right: var(--size-1);
1095
+ top: 50%;
1096
+ transform: translateY(-50%);
1084
1097
  }
1085
1098
 
1086
- .cell-menu-button:hover {
1087
- background-color: var(--color-bg-hover);
1099
+ .cell-selected .cell-menu-button {
1100
+ display: flex;
1101
+ align-items: center;
1102
+ justify-content: center;
1088
1103
  }
1089
1104
 
1090
- td.focus .cell-menu-button {
1105
+ .header-row {
1091
1106
  display: flex;
1107
+ justify-content: space-between;
1092
1108
  align-items: center;
1093
- justify-content: center;
1109
+ gap: var(--size-2);
1110
+ height: var(--size-6);
1111
+ min-height: var(--size-6);
1094
1112
  }
1095
1113
 
1096
- th .header-content {
1097
- white-space: normal;
1098
- overflow-wrap: break-word;
1099
- word-break: break-word;
1114
+ .label {
1115
+ flex: 1;
1100
1116
  }
1101
1117
 
1102
- .table-container {
1103
- display: flex;
1104
- flex-direction: column;
1105
- gap: var(--size-2);
1118
+ .label p {
1119
+ margin: 0;
1120
+ color: var(--block-label-text-color);
1121
+ font-size: var(--block-label-text-size);
1106
1122
  }
1107
1123
 
1108
1124
  .row-number,
@@ -1134,24 +1150,94 @@ afterUpdate(() => {
1134
1150
  background: var(--table-odd-background-fill);
1135
1151
  }
1136
1152
 
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);
1153
+ .cell-selected {
1154
+ --ring-color: var(--color-accent);
1155
+ box-shadow: inset 0 0 0 2px var(--ring-color);
1156
+ z-index: var(--layer-1);
1157
+ position: relative;
1144
1158
  }
1145
1159
 
1146
- .label {
1147
- flex: 1;
1160
+ .cell-selected.no-top {
1161
+ box-shadow:
1162
+ inset 2px 0 0 var(--ring-color),
1163
+ inset -2px 0 0 var(--ring-color),
1164
+ inset 0 -2px 0 var(--ring-color);
1148
1165
  }
1149
1166
 
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);
1167
+ .cell-selected.no-bottom {
1168
+ box-shadow:
1169
+ inset 2px 0 0 var(--ring-color),
1170
+ inset -2px 0 0 var(--ring-color),
1171
+ inset 0 2px 0 var(--ring-color);
1172
+ }
1173
+
1174
+ .cell-selected.no-left {
1175
+ box-shadow:
1176
+ inset 0 2px 0 var(--ring-color),
1177
+ inset -2px 0 0 var(--ring-color),
1178
+ inset 0 -2px 0 var(--ring-color);
1179
+ }
1180
+
1181
+ .cell-selected.no-right {
1182
+ box-shadow:
1183
+ inset 0 2px 0 var(--ring-color),
1184
+ inset 2px 0 0 var(--ring-color),
1185
+ inset 0 -2px 0 var(--ring-color);
1186
+ }
1187
+
1188
+ .cell-selected.no-top.no-left {
1189
+ box-shadow:
1190
+ inset -2px 0 0 var(--ring-color),
1191
+ inset 0 -2px 0 var(--ring-color);
1192
+ }
1193
+
1194
+ .cell-selected.no-top.no-right {
1195
+ box-shadow:
1196
+ inset 2px 0 0 var(--ring-color),
1197
+ inset 0 -2px 0 var(--ring-color);
1198
+ }
1199
+
1200
+ .cell-selected.no-bottom.no-left {
1201
+ box-shadow:
1202
+ inset -2px 0 0 var(--ring-color),
1203
+ inset 0 2px 0 var(--ring-color);
1204
+ }
1205
+
1206
+ .cell-selected.no-bottom.no-right {
1207
+ box-shadow:
1208
+ inset 2px 0 0 var(--ring-color),
1209
+ inset 0 2px 0 var(--ring-color);
1210
+ }
1211
+
1212
+ .cell-selected.no-top.no-bottom {
1213
+ box-shadow:
1214
+ inset 2px 0 0 var(--ring-color),
1215
+ inset -2px 0 0 var(--ring-color);
1216
+ }
1217
+
1218
+ .cell-selected.no-left.no-right {
1219
+ box-shadow:
1220
+ inset 0 2px 0 var(--ring-color),
1221
+ inset 0 -2px 0 var(--ring-color);
1222
+ }
1223
+
1224
+ .cell-selected.no-top.no-left.no-right {
1225
+ box-shadow: inset 0 -2px 0 var(--ring-color);
1226
+ }
1227
+
1228
+ .cell-selected.no-bottom.no-left.no-right {
1229
+ box-shadow: inset 0 2px 0 var(--ring-color);
1230
+ }
1231
+
1232
+ .cell-selected.no-left.no-top.no-bottom {
1233
+ box-shadow: inset -2px 0 0 var(--ring-color);
1234
+ }
1235
+
1236
+ .cell-selected.no-right.no-top.no-bottom {
1237
+ box-shadow: inset 2px 0 0 var(--ring-color);
1238
+ }
1239
+
1240
+ .cell-selected.no-top.no-bottom.no-left.no-right {
1241
+ box-shadow: none;
1156
1242
  }
1157
1243
  </style>