@gradio/dataframe 0.15.0 → 0.16.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/Dataframe.stories.svelte +168 -2
  3. package/Index.svelte +20 -3
  4. package/dist/Index.svelte +16 -4
  5. package/dist/Index.svelte.d.ts +12 -0
  6. package/dist/shared/EditableCell.svelte +1 -4
  7. package/dist/shared/Table.svelte +423 -181
  8. package/dist/shared/Table.svelte.d.ts +3 -0
  9. package/dist/shared/Toolbar.svelte +122 -30
  10. package/dist/shared/Toolbar.svelte.d.ts +4 -0
  11. package/dist/shared/VirtualTable.svelte +70 -26
  12. package/dist/shared/VirtualTable.svelte.d.ts +1 -0
  13. package/dist/shared/icons/FilterIcon.svelte +11 -0
  14. package/dist/shared/icons/FilterIcon.svelte.d.ts +16 -0
  15. package/dist/shared/icons/SortIcon.svelte +90 -0
  16. package/dist/shared/icons/SortIcon.svelte.d.ts +20 -0
  17. package/dist/shared/selection_utils.d.ts +12 -2
  18. package/dist/shared/selection_utils.js +33 -5
  19. package/dist/shared/types.d.ts +16 -0
  20. package/dist/shared/types.js +1 -0
  21. package/dist/shared/utils/menu_utils.d.ts +42 -0
  22. package/dist/shared/utils/menu_utils.js +58 -0
  23. package/dist/shared/utils/sort_utils.d.ts +7 -0
  24. package/dist/shared/utils/sort_utils.js +39 -0
  25. package/dist/shared/utils/table_utils.d.ts +12 -0
  26. package/dist/shared/utils/table_utils.js +148 -0
  27. package/package.json +8 -8
  28. package/shared/EditableCell.svelte +1 -4
  29. package/shared/Table.svelte +453 -182
  30. package/shared/Toolbar.svelte +125 -30
  31. package/shared/VirtualTable.svelte +73 -26
  32. package/shared/icons/FilterIcon.svelte +12 -0
  33. package/shared/icons/SortIcon.svelte +95 -0
  34. package/shared/selection_utils.ts +51 -9
  35. package/shared/types.ts +27 -0
  36. package/shared/utils/menu_utils.ts +115 -0
  37. package/shared/utils/sort_utils.test.ts +71 -0
  38. package/shared/utils/sort_utils.ts +55 -0
  39. package/shared/utils/table_utils.test.ts +114 -0
  40. package/shared/utils/table_utils.ts +206 -0
  41. package/dist/shared/table_utils.d.ts +0 -12
  42. package/dist/shared/table_utils.js +0 -113
  43. package/shared/table_utils.ts +0 -148
@@ -6,6 +6,7 @@ import {} from "@gradio/client";
6
6
  import VirtualTable from "./VirtualTable.svelte";
7
7
  import CellMenu from "./CellMenu.svelte";
8
8
  import Toolbar from "./Toolbar.svelte";
9
+ import SortIcon from "./icons/SortIcon.svelte";
9
10
  import {
10
11
  is_cell_selected,
11
12
  handle_selection,
@@ -15,9 +16,17 @@ import {
15
16
  get_range_selection,
16
17
  move_cursor,
17
18
  get_current_indices,
18
- handle_click_outside as handle_click_outside_util
19
+ handle_click_outside as handle_click_outside_util,
20
+ select_column,
21
+ select_row,
22
+ calculate_selection_positions
19
23
  } from "./selection_utils";
20
- import { copy_table_data, get_max, handle_file_upload } from "./table_utils";
24
+ import {
25
+ copy_table_data,
26
+ get_max,
27
+ handle_file_upload,
28
+ sort_table_data
29
+ } from "./utils/table_utils";
21
30
  export let datatype;
22
31
  export let label = null;
23
32
  export let show_label = true;
@@ -40,6 +49,11 @@ export let show_fullscreen_button = false;
40
49
  export let show_copy_button = false;
41
50
  export let value_is_output = false;
42
51
  export let max_chars = void 0;
52
+ export let show_search = "none";
53
+ export let pinned_columns = 0;
54
+ let actual_pinned_columns = 0;
55
+ $:
56
+ actual_pinned_columns = pinned_columns && data?.[0]?.length ? Math.min(pinned_columns, data[0].length) : 0;
43
57
  let selected_cells = [];
44
58
  $:
45
59
  selected_cells = [...selected_cells];
@@ -60,33 +74,43 @@ let active_cell_menu = null;
60
74
  let active_header_menu = null;
61
75
  let is_fullscreen = false;
62
76
  let dragging = false;
77
+ let copy_flash = false;
78
+ let color_accent_copied;
79
+ onMount(() => {
80
+ const color = getComputedStyle(document.documentElement).getPropertyValue("--color-accent").trim();
81
+ color_accent_copied = color + "40";
82
+ document.documentElement.style.setProperty(
83
+ "--color-accent-copied",
84
+ color_accent_copied
85
+ );
86
+ });
63
87
  const get_data_at = (row, col) => data?.[row]?.[col]?.value;
64
88
  function make_id() {
65
89
  return Math.random().toString(36).substring(2, 15);
66
90
  }
67
- function make_headers(_head) {
91
+ function make_headers(_head, col_count2, els2) {
68
92
  let _h = _head || [];
69
- if (col_count[1] === "fixed" && _h.length < col_count[0]) {
70
- const fill = Array(col_count[0] - _h.length).fill("").map((_, i) => `${i + _h.length}`);
93
+ if (col_count2[1] === "fixed" && _h.length < col_count2[0]) {
94
+ const fill = Array(col_count2[0] - _h.length).fill("").map((_, i) => `${i + _h.length}`);
71
95
  _h = _h.concat(fill);
72
96
  }
73
97
  if (!_h || _h.length === 0) {
74
- return Array(col_count[0]).fill(0).map((_, i) => {
98
+ return Array(col_count2[0]).fill(0).map((_, i) => {
75
99
  const _id = make_id();
76
- els[_id] = { cell: null, input: null };
100
+ els2[_id] = { cell: null, input: null };
77
101
  return { id: _id, value: JSON.stringify(i + 1) };
78
102
  });
79
103
  }
80
104
  return _h.map((h, i) => {
81
105
  const _id = make_id();
82
- els[_id] = { cell: null, input: null };
106
+ els2[_id] = { cell: null, input: null };
83
107
  return { id: _id, value: h ?? "" };
84
108
  });
85
109
  }
86
110
  function process_data(_values) {
87
111
  const data_row_length = _values.length;
88
- return Array(row_count[1] === "fixed" ? row_count[0] : data_row_length).fill(0).map(
89
- (_, i) => Array(
112
+ return Array(row_count[1] === "fixed" ? row_count[0] : data_row_length).fill(0).map((_, i) => {
113
+ return Array(
90
114
  col_count[1] === "fixed" ? col_count[0] : data_row_length > 0 ? _values[0].length : headers.length
91
115
  ).fill(0).map((_2, j) => {
92
116
  const id = make_id();
@@ -94,14 +118,14 @@ function process_data(_values) {
94
118
  const obj = { value: _values?.[i]?.[j] ?? "", id };
95
119
  data_binding[id] = obj;
96
120
  return obj;
97
- })
98
- );
121
+ });
122
+ });
99
123
  }
100
- let _headers = make_headers(headers);
124
+ let _headers = make_headers(headers, col_count, els);
101
125
  let old_headers = headers;
102
126
  $: {
103
127
  if (!dequal(headers, old_headers)) {
104
- _headers = make_headers(headers);
128
+ _headers = make_headers(headers, col_count, els);
105
129
  old_headers = JSON.parse(JSON.stringify(headers));
106
130
  }
107
131
  }
@@ -115,6 +139,8 @@ $:
115
139
  let previous_headers = _headers.map((h) => h.value);
116
140
  let previous_data = data.map((row) => row.map((cell) => String(cell.value)));
117
141
  async function trigger_change() {
142
+ if (current_search_query)
143
+ return;
118
144
  const current_headers = _headers.map((h) => h.value);
119
145
  const current_data = data.map(
120
146
  (row) => row.map((cell) => String(cell.value))
@@ -224,8 +250,10 @@ async function handle_keydown(event) {
224
250
  editing = false;
225
251
  } else {
226
252
  selected_cells = [next_coords];
227
- editing = next_coords;
228
- clear_on_focus = false;
253
+ if (editable) {
254
+ editing = next_coords;
255
+ clear_on_focus = false;
256
+ }
229
257
  }
230
258
  selected = next_coords;
231
259
  } else if (next_coords === false && event.key === "ArrowUp" && i === 0) {
@@ -241,26 +269,26 @@ async function handle_keydown(event) {
241
269
  editing = false;
242
270
  break;
243
271
  case "Enter":
244
- if (!editable)
245
- break;
246
272
  event.preventDefault();
247
- if (event.shiftKey) {
248
- add_row(i);
249
- await tick();
250
- selected = [i + 1, j];
251
- } else {
252
- if (dequal(editing, [i, j])) {
253
- const cell_id = data[i][j].id;
254
- const input_el = els[cell_id].input;
255
- if (input_el) {
256
- data[i][j].value = input_el.value;
257
- }
258
- editing = false;
273
+ if (editable) {
274
+ if (event.shiftKey) {
275
+ add_row(i);
259
276
  await tick();
260
- selected = [i, j];
277
+ selected = [i + 1, j];
261
278
  } else {
262
- editing = [i, j];
263
- clear_on_focus = false;
279
+ if (dequal(editing, [i, j])) {
280
+ const cell_id = data[i][j].id;
281
+ const input_el = els[cell_id].input;
282
+ if (input_el) {
283
+ data[i][j].value = input_el.value;
284
+ }
285
+ editing = false;
286
+ await tick();
287
+ selected = [i, j];
288
+ } else {
289
+ editing = [i, j];
290
+ clear_on_focus = false;
291
+ }
264
292
  }
265
293
  }
266
294
  break;
@@ -292,15 +320,16 @@ async function handle_keydown(event) {
292
320
  }
293
321
  let sort_direction;
294
322
  let sort_by;
295
- function handle_sort(col) {
323
+ function handle_sort(col, direction) {
296
324
  if (typeof sort_by !== "number" || sort_by !== col) {
297
- sort_direction = "asc";
325
+ sort_direction = direction;
298
326
  sort_by = col;
299
- } else {
300
- if (sort_direction === "asc") {
301
- sort_direction = "des";
302
- } else if (sort_direction === "des") {
303
- sort_direction = "asc";
327
+ } else if (sort_by === col) {
328
+ if (sort_direction === direction) {
329
+ sort_direction = void 0;
330
+ sort_by = void 0;
331
+ } else {
332
+ sort_direction = direction;
304
333
  }
305
334
  }
306
335
  }
@@ -388,50 +417,36 @@ let cells = [];
388
417
  let parent;
389
418
  let table;
390
419
  function set_cell_widths() {
391
- const widths = cells.map((el, i) => {
392
- return el?.clientWidth || 0;
393
- });
420
+ const widths = cells.map((el) => el?.clientWidth || 0);
394
421
  if (widths.length === 0)
395
422
  return;
396
- for (let i = 0; i < widths.length; i++) {
397
- parent.style.setProperty(
398
- `--cell-width-${i}`,
399
- `${widths[i] - scrollbar_width / widths.length}px`
400
- );
423
+ if (show_row_numbers) {
424
+ parent.style.setProperty(`--cell-width-row-number`, `${widths[0]}px`);
401
425
  }
426
+ const data_cells = show_row_numbers ? widths.slice(1) : widths;
427
+ data_cells.forEach((width, i) => {
428
+ if (!column_widths[i]) {
429
+ parent.style.setProperty(
430
+ `--cell-width-${i}`,
431
+ `${width - scrollbar_width / data_cells.length}px`
432
+ );
433
+ }
434
+ });
435
+ }
436
+ function get_cell_width(index) {
437
+ return column_widths[index] || `var(--cell-width-${index})`;
402
438
  }
403
439
  let table_height = values.slice(0, max_height / values.length * 37).length * 37 + 37;
404
440
  let scrollbar_width = 0;
405
441
  function sort_data(_data, _display_value, _styling, col, dir) {
406
442
  let id = null;
407
- if (selected && selected[0] in data && selected[1] in data[selected[0]]) {
408
- id = data[selected[0]][selected[1]].id;
443
+ if (selected && selected[0] in _data && selected[1] in _data[selected[0]]) {
444
+ id = _data[selected[0]][selected[1]].id;
409
445
  }
410
446
  if (typeof col !== "number" || !dir) {
411
447
  return;
412
448
  }
413
- const indices = [...Array(_data.length).keys()];
414
- if (dir === "asc") {
415
- indices.sort(
416
- (i, j) => _data[i][col].value < _data[j][col].value ? -1 : 1
417
- );
418
- } else if (dir === "des") {
419
- indices.sort(
420
- (i, j) => _data[i][col].value > _data[j][col].value ? -1 : 1
421
- );
422
- } else {
423
- return;
424
- }
425
- const temp_data = [..._data];
426
- const temp_display_value = _display_value ? [..._display_value] : null;
427
- const temp_styling = _styling ? [..._styling] : null;
428
- indices.forEach((originalIndex, sortedIndex) => {
429
- _data[sortedIndex] = temp_data[originalIndex];
430
- if (_display_value && temp_display_value)
431
- _display_value[sortedIndex] = temp_display_value[originalIndex];
432
- if (_styling && temp_styling)
433
- _styling[sortedIndex] = temp_styling[originalIndex];
434
- });
449
+ sort_table_data(_data, _display_value, _styling, col, dir);
435
450
  data = data;
436
451
  if (id) {
437
452
  const [i, j] = get_current_indices(id, data);
@@ -468,25 +483,33 @@ onMount(() => {
468
483
  };
469
484
  });
470
485
  function handle_cell_click(event, row, col) {
486
+ if (event.target instanceof HTMLAnchorElement) {
487
+ return;
488
+ }
471
489
  event.preventDefault();
472
490
  event.stopPropagation();
491
+ if (show_row_numbers && col === -1)
492
+ return;
473
493
  clear_on_focus = false;
474
494
  active_cell_menu = null;
475
495
  active_header_menu = null;
476
496
  selected_header = false;
477
497
  header_edit = false;
478
498
  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;
499
+ parent.focus();
500
+ if (editable) {
501
+ if (selected_cells.length === 1) {
502
+ editing = [row, col];
503
+ tick().then(() => {
504
+ const input_el = els[data[row][col].id].input;
505
+ if (input_el) {
506
+ input_el.focus();
507
+ input_el.selectionStart = input_el.selectionEnd = input_el.value.length;
508
+ }
509
+ });
510
+ } else {
511
+ editing = false;
512
+ }
490
513
  }
491
514
  toggle_cell_button(row, col);
492
515
  dispatch("select", {
@@ -522,6 +545,9 @@ function add_col_at(index, position) {
522
545
  function handle_resize() {
523
546
  active_cell_menu = null;
524
547
  active_header_menu = null;
548
+ selected_cells = [];
549
+ selected = false;
550
+ editing = false;
525
551
  set_cell_widths();
526
552
  }
527
553
  let active_button = null;
@@ -544,7 +570,11 @@ function handle_fullscreen_change() {
544
570
  is_fullscreen = !!document.fullscreenElement;
545
571
  }
546
572
  async function handle_copy() {
547
- await copy_table_data(data, _headers, selected_cells);
573
+ await copy_table_data(data, selected_cells);
574
+ copy_flash = true;
575
+ setTimeout(() => {
576
+ copy_flash = false;
577
+ }, 800);
548
578
  }
549
579
  function toggle_header_menu(event, col) {
550
580
  event.stopPropagation();
@@ -595,6 +625,78 @@ function delete_col_at(index) {
595
625
  active_cell_menu = null;
596
626
  active_header_menu = null;
597
627
  }
628
+ let row_order = [];
629
+ $: {
630
+ if (typeof sort_by === "number" && sort_direction && sort_by >= 0 && sort_by < data[0].length) {
631
+ const indices = [...Array(data.length)].map((_, i) => i);
632
+ const sort_index = sort_by;
633
+ indices.sort((a, b) => {
634
+ const row_a = data[a];
635
+ const row_b = data[b];
636
+ if (!row_a || !row_b || sort_index >= row_a.length || sort_index >= row_b.length)
637
+ return 0;
638
+ const val_a = row_a[sort_index].value;
639
+ const val_b = row_b[sort_index].value;
640
+ const comp = val_a < val_b ? -1 : val_a > val_b ? 1 : 0;
641
+ return sort_direction === "asc" ? comp : -comp;
642
+ });
643
+ row_order = indices;
644
+ } else {
645
+ row_order = [...Array(data.length)].map((_, i) => i);
646
+ }
647
+ }
648
+ function handle_select_column(col) {
649
+ selected_cells = select_column(data, col);
650
+ selected = selected_cells[0];
651
+ editing = false;
652
+ }
653
+ function handle_select_row(row) {
654
+ selected_cells = select_row(data, row);
655
+ selected = selected_cells[0];
656
+ editing = false;
657
+ }
658
+ let coords;
659
+ $:
660
+ if (selected !== false)
661
+ coords = selected;
662
+ $:
663
+ if (selected !== false) {
664
+ const positions = calculate_selection_positions(
665
+ selected,
666
+ data,
667
+ els,
668
+ parent,
669
+ table
670
+ );
671
+ document.documentElement.style.setProperty(
672
+ "--selected-col-pos",
673
+ positions.col_pos
674
+ );
675
+ if (positions.row_pos) {
676
+ document.documentElement.style.setProperty(
677
+ "--selected-row-pos",
678
+ positions.row_pos
679
+ );
680
+ }
681
+ }
682
+ let current_search_query = null;
683
+ function handle_search(search_query) {
684
+ current_search_query = search_query;
685
+ dispatch("search", search_query);
686
+ }
687
+ function commit_filter() {
688
+ if (current_search_query && show_search === "filter") {
689
+ dispatch("change", {
690
+ data: data.map((row) => row.map((cell) => cell.value)),
691
+ headers: _headers.map((h) => h.value),
692
+ metadata: null
693
+ });
694
+ if (!value_is_output) {
695
+ dispatch("input");
696
+ }
697
+ current_search_query = null;
698
+ }
699
+ }
598
700
  </script>
599
701
 
600
702
  <svelte:window on:resize={() => set_cell_widths()} />
@@ -612,6 +714,10 @@ function delete_col_at(index) {
612
714
  on:click={toggle_fullscreen}
613
715
  on_copy={handle_copy}
614
716
  {show_copy_button}
717
+ {show_search}
718
+ on:search={(e) => handle_search(e.detail)}
719
+ on_commit_filter={commit_filter}
720
+ {current_search_query}
615
721
  />
616
722
  </div>
617
723
  <div
@@ -619,11 +725,28 @@ function delete_col_at(index) {
619
725
  class="table-wrap"
620
726
  class:dragging
621
727
  class:no-wrap={!wrap}
622
- style="height:{table_height}px"
728
+ style="height:{table_height}px;"
729
+ class:menu-open={active_cell_menu || active_header_menu}
623
730
  on:keydown={(e) => handle_keydown(e)}
624
731
  role="grid"
625
732
  tabindex="0"
626
733
  >
734
+ {#if selected !== false && selected_cells.length === 1}
735
+ <button
736
+ class="selection-button selection-button-column"
737
+ on:click|stopPropagation={() => handle_select_column(coords[1])}
738
+ aria-label="Select column"
739
+ >
740
+ &#8942;
741
+ </button>
742
+ <button
743
+ class="selection-button selection-button-row"
744
+ on:click|stopPropagation={() => handle_select_row(coords[0])}
745
+ aria-label="Select row"
746
+ >
747
+ &#8942;
748
+ </button>
749
+ {/if}
627
750
  <table
628
751
  bind:contentRect={t_rect}
629
752
  bind:this={table}
@@ -635,40 +758,59 @@ function delete_col_at(index) {
635
758
  <thead>
636
759
  <tr>
637
760
  {#if show_row_numbers}
638
- <th class="row-number-header"></th>
761
+ <th
762
+ class="row-number-header frozen-column always-frozen"
763
+ style="left: 0;"
764
+ >
765
+ <div class="cell-wrap">
766
+ <div class="header-content">
767
+ <div class="header-text"></div>
768
+ </div>
769
+ </div>
770
+ </th>
639
771
  {/if}
640
772
  {#each _headers as { value, id }, i (id)}
641
773
  <th
774
+ class:frozen-column={i < actual_pinned_columns}
775
+ class:last-frozen={show_row_numbers
776
+ ? i === actual_pinned_columns - 1
777
+ : i === actual_pinned_columns - 1}
642
778
  class:editing={header_edit === i}
643
779
  aria-sort={get_sort_status(value, sort_by, sort_direction)}
644
- style:width={column_widths.length ? column_widths[i] : undefined}
780
+ style="width: {column_widths.length
781
+ ? column_widths[i]
782
+ : undefined}; left: {i < actual_pinned_columns
783
+ ? i === 0
784
+ ? show_row_numbers
785
+ ? 'var(--cell-width-row-number)'
786
+ : '0'
787
+ : `calc(${show_row_numbers ? 'var(--cell-width-row-number) + ' : ''}${Array(
788
+ i
789
+ )
790
+ .fill(0)
791
+ .map((_, idx) => `var(--cell-width-${idx})`)
792
+ .join(' + ')})`
793
+ : 'auto'};"
645
794
  >
646
795
  <div class="cell-wrap">
647
- <EditableCell
648
- {value}
649
- {latex_delimiters}
650
- {line_breaks}
651
- header
652
- edit={false}
653
- el={null}
654
- {root}
655
- {editable}
656
- />
657
-
658
- <div
659
- class:sorted={sort_by === i}
660
- class:des={sort_by === i && sort_direction === "des"}
661
- class="sort-button {sort_direction} "
662
- >
663
- <svg
664
- width="1em"
665
- height="1em"
666
- viewBox="0 0 9 7"
667
- fill="none"
668
- xmlns="http://www.w3.org/2000/svg"
669
- >
670
- <path d="M4.49999 0L8.3971 6.75H0.602875L4.49999 0Z" />
671
- </svg>
796
+ <div class="header-content">
797
+ <EditableCell
798
+ {value}
799
+ {latex_delimiters}
800
+ {line_breaks}
801
+ header
802
+ edit={false}
803
+ el={null}
804
+ {root}
805
+ {editable}
806
+ />
807
+ <div class="sort-buttons">
808
+ <SortIcon
809
+ direction={sort_by === i ? sort_direction : null}
810
+ on:sort={({ detail }) => handle_sort(i, detail)}
811
+ {i18n}
812
+ />
813
+ </div>
672
814
  </div>
673
815
  </div>
674
816
  </th>
@@ -707,9 +849,12 @@ function delete_col_at(index) {
707
849
  on:load={({ detail }) =>
708
850
  handle_file_upload(
709
851
  detail.data,
710
- col_count,
711
852
  (head) => {
712
- _headers = make_headers(head);
853
+ _headers = make_headers(
854
+ head.map((h) => h ?? ""),
855
+ col_count,
856
+ els
857
+ );
713
858
  return _headers;
714
859
  },
715
860
  (vals) => {
@@ -725,19 +870,44 @@ function delete_col_at(index) {
725
870
  bind:actual_height={table_height}
726
871
  bind:table_scrollbar_width={scrollbar_width}
727
872
  selected={selected_index}
873
+ disable_scroll={active_cell_menu !== null ||
874
+ active_header_menu !== null}
728
875
  >
729
876
  {#if label && label.length !== 0}
730
877
  <caption class="sr-only">{label}</caption>
731
878
  {/if}
732
879
  <tr slot="thead">
733
880
  {#if show_row_numbers}
734
- <th class="row-number-header"></th>
881
+ <th
882
+ class="row-number-header frozen-column always-frozen"
883
+ style="left: 0;"
884
+ >
885
+ <div class="cell-wrap">
886
+ <div class="header-content">
887
+ <div class="header-text"></div>
888
+ </div>
889
+ </div>
890
+ </th>
735
891
  {/if}
736
892
  {#each _headers as { value, id }, i (id)}
737
893
  <th
894
+ class:frozen-column={i < actual_pinned_columns}
895
+ class:last-frozen={i === actual_pinned_columns - 1}
738
896
  class:focus={header_edit === i || selected_header === i}
739
897
  aria-sort={get_sort_status(value, sort_by, sort_direction)}
740
- style="width: var(--cell-width-{i});"
898
+ style="width: {get_cell_width(i)}; left: {i <
899
+ actual_pinned_columns
900
+ ? i === 0
901
+ ? show_row_numbers
902
+ ? 'var(--cell-width-row-number)'
903
+ : '0'
904
+ : `calc(${show_row_numbers ? 'var(--cell-width-row-number) + ' : ''}${Array(
905
+ i
906
+ )
907
+ .fill(0)
908
+ .map((_, idx) => `var(--cell-width-${idx})`)
909
+ .join(' + ')})`
910
+ : 'auto'};"
741
911
  on:click={() => {
742
912
  toggle_header_button(i);
743
913
  }}
@@ -757,28 +927,14 @@ function delete_col_at(index) {
757
927
  {root}
758
928
  {editable}
759
929
  />
760
- <button
761
- class:sorted={sort_by === i}
762
- class:des={sort_by === i && sort_direction === "des"}
763
- class="sort-button {sort_direction}"
764
- tabindex="0"
765
- on:click={(event) => {
766
- event.stopPropagation();
767
- handle_sort(i);
768
- }}
769
- >
770
- <svg
771
- width="1em"
772
- height="1em"
773
- viewBox="0 0 9 7"
774
- fill="none"
775
- xmlns="http://www.w3.org/2000/svg"
776
- >
777
- <path d="M4.49999 0L8.3971 6.75H0.602875L4.49999 0Z" />
778
- </svg>
779
- </button>
930
+ <div class="sort-buttons">
931
+ <SortIcon
932
+ direction={sort_by === i ? sort_direction : null}
933
+ on:sort={({ detail }) => handle_sort(i, detail)}
934
+ {i18n}
935
+ />
936
+ </div>
780
937
  </div>
781
-
782
938
  {#if editable}
783
939
  <button
784
940
  class="cell-menu-button"
@@ -791,14 +947,22 @@ function delete_col_at(index) {
791
947
  </th>
792
948
  {/each}
793
949
  </tr>
794
-
795
950
  <tr slot="tbody" let:item let:index class:row_odd={index % 2 === 0}>
796
951
  {#if show_row_numbers}
797
- <td class="row-number" title={`Row ${index + 1}`}>{index + 1}</td>
952
+ <td
953
+ class="row-number frozen-column always-frozen"
954
+ style="left: 0;"
955
+ tabindex="-1"
956
+ >
957
+ {index + 1}
958
+ </td>
798
959
  {/if}
799
960
  {#each item as { value, id }, j (id)}
800
961
  <td
801
- tabindex="0"
962
+ class:frozen-column={j < actual_pinned_columns}
963
+ class:last-frozen={j === actual_pinned_columns - 1}
964
+ tabindex={show_row_numbers && j === 0 ? -1 : 0}
965
+ bind:this={els[id].cell}
802
966
  on:touchstart={(event) => {
803
967
  const touch = event.touches[0];
804
968
  const mouseEvent = new MouseEvent("click", {
@@ -815,8 +979,21 @@ function delete_col_at(index) {
815
979
  event.stopPropagation();
816
980
  }}
817
981
  on:click={(event) => handle_cell_click(event, index, j)}
818
- style:width="var(--cell-width-{j})"
819
- style={styling?.[index]?.[j] || ""}
982
+ style="width: {get_cell_width(j)}; left: {j <
983
+ actual_pinned_columns
984
+ ? j === 0
985
+ ? show_row_numbers
986
+ ? 'var(--cell-width-row-number)'
987
+ : '0'
988
+ : `calc(${show_row_numbers ? 'var(--cell-width-row-number) + ' : ''}${Array(
989
+ j
990
+ )
991
+ .fill(0)
992
+ .map((_, idx) => `var(--cell-width-${idx})`)
993
+ .join(' + ')})`
994
+ : 'auto'}; {styling?.[index]?.[j] || ''}"
995
+ class:flash={copy_flash &&
996
+ is_cell_selected([index, j], selected_cells)}
820
997
  class={is_cell_selected([index, j], selected_cells)}
821
998
  class:menu-active={active_cell_menu &&
822
999
  active_cell_menu.row === index &&
@@ -906,6 +1083,14 @@ function delete_col_at(index) {
906
1083
  {/if}
907
1084
 
908
1085
  <style>
1086
+ .label p {
1087
+ position: relative;
1088
+ z-index: var(--layer-4);
1089
+ margin-bottom: var(--size-2);
1090
+ color: var(--block-label-text-color);
1091
+ font-size: var(--block-label-text-size);
1092
+ }
1093
+
909
1094
  .table-container {
910
1095
  display: flex;
911
1096
  flex-direction: column;
@@ -917,6 +1102,9 @@ function delete_col_at(index) {
917
1102
  transition: 150ms;
918
1103
  border: 1px solid var(--border-color-primary);
919
1104
  border-radius: var(--table-radius);
1105
+ }
1106
+
1107
+ .table-wrap.menu-open {
920
1108
  overflow: hidden;
921
1109
  }
922
1110
 
@@ -961,8 +1149,7 @@ function delete_col_at(index) {
961
1149
  thead {
962
1150
  position: sticky;
963
1151
  top: 0;
964
- left: 0;
965
- z-index: var(--layer-1);
1152
+ z-index: var(--layer-2);
966
1153
  box-shadow: var(--shadow-drop);
967
1154
  }
968
1155
 
@@ -1018,32 +1205,10 @@ function delete_col_at(index) {
1018
1205
  background: var(--table-even-background-fill);
1019
1206
  }
1020
1207
 
1021
- th svg {
1022
- fill: currentColor;
1023
- font-size: 10px;
1024
- }
1025
-
1026
- .sort-button {
1208
+ .sort-buttons {
1027
1209
  display: flex;
1028
- flex: none;
1029
- justify-content: center;
1030
1210
  align-items: center;
1031
- transition: 150ms;
1032
- cursor: pointer;
1033
- padding: var(--size-2);
1034
- color: var(--body-text-color-subdued);
1035
- }
1036
-
1037
- .sort-button:hover {
1038
- color: var(--body-text-color);
1039
- }
1040
-
1041
- .des {
1042
- transform: scaleY(-1);
1043
- }
1044
-
1045
- .sort-button.sorted {
1046
- color: var(--color-accent);
1211
+ flex-shrink: 0;
1047
1212
  }
1048
1213
 
1049
1214
  .editing {
@@ -1062,12 +1227,16 @@ function delete_col_at(index) {
1062
1227
  .header-content {
1063
1228
  display: flex;
1064
1229
  align-items: center;
1230
+ justify-content: space-between;
1065
1231
  overflow: hidden;
1066
1232
  flex-grow: 1;
1067
1233
  min-width: 0;
1068
1234
  white-space: normal;
1069
1235
  overflow-wrap: break-word;
1070
- word-break: break-word;
1236
+ word-break: normal;
1237
+ height: 100%;
1238
+ padding: var(--size-1);
1239
+ gap: var(--size-1);
1071
1240
  }
1072
1241
 
1073
1242
  .row_odd {
@@ -1104,46 +1273,55 @@ function delete_col_at(index) {
1104
1273
 
1105
1274
  .header-row {
1106
1275
  display: flex;
1107
- justify-content: space-between;
1276
+ justify-content: flex-end;
1108
1277
  align-items: center;
1109
1278
  gap: var(--size-2);
1110
- height: var(--size-6);
1111
1279
  min-height: var(--size-6);
1280
+ flex-wrap: nowrap;
1281
+ width: 100%;
1112
1282
  }
1113
1283
 
1114
1284
  .label {
1115
- flex: 1;
1285
+ flex: 1 1 auto;
1286
+ margin-right: auto;
1116
1287
  }
1117
1288
 
1118
1289
  .label p {
1119
1290
  margin: 0;
1120
1291
  color: var(--block-label-text-color);
1121
1292
  font-size: var(--block-label-text-size);
1293
+ line-height: var(--line-sm);
1294
+ }
1295
+
1296
+ .toolbar {
1297
+ flex: 0 0 auto;
1122
1298
  }
1123
1299
 
1124
1300
  .row-number,
1125
1301
  .row-number-header {
1126
- width: var(--size-7);
1127
- min-width: var(--size-7);
1128
1302
  text-align: center;
1129
1303
  background: var(--table-even-background-fill);
1130
- position: sticky;
1131
- left: 0;
1132
1304
  font-size: var(--input-text-size);
1133
1305
  color: var(--body-text-color);
1134
- padding: var(--size-1) var(--size-2);
1306
+ padding: var(--size-1);
1307
+ min-width: var(--size-12);
1308
+ width: var(--size-12);
1135
1309
  overflow: hidden;
1136
1310
  text-overflow: ellipsis;
1137
1311
  white-space: nowrap;
1138
1312
  font-weight: var(--weight-semibold);
1139
1313
  }
1140
1314
 
1141
- .row-number-header {
1142
- z-index: var(--layer-2);
1315
+ .row-number-header .header-content {
1316
+ justify-content: space-between;
1317
+ padding: var(--size-1);
1318
+ height: var(--size-9);
1319
+ display: flex;
1320
+ align-items: center;
1143
1321
  }
1144
1322
 
1145
- .row-number {
1146
- z-index: var(--layer-1);
1323
+ .row-number-header :global(.sort-icons) {
1324
+ margin-right: 0;
1147
1325
  }
1148
1326
 
1149
1327
  :global(tbody > tr:nth-child(odd)) .row-number {
@@ -1240,4 +1418,68 @@ function delete_col_at(index) {
1240
1418
  .cell-selected.no-top.no-bottom.no-left.no-right {
1241
1419
  box-shadow: none;
1242
1420
  }
1421
+
1422
+ .selection-button {
1423
+ position: absolute;
1424
+ display: flex;
1425
+ align-items: center;
1426
+ justify-content: center;
1427
+ background: var(--color-accent);
1428
+ color: white;
1429
+ border-radius: var(--radius-sm);
1430
+ z-index: var(--layer-4);
1431
+ }
1432
+
1433
+ .selection-button-column {
1434
+ width: var(--size-3);
1435
+ height: var(--size-5);
1436
+ top: -10px;
1437
+ left: var(--selected-col-pos);
1438
+ transform: rotate(90deg);
1439
+ }
1440
+
1441
+ .selection-button-row {
1442
+ width: var(--size-3);
1443
+ height: var(--size-5);
1444
+ left: -7px;
1445
+ top: calc(var(--selected-row-pos) - var(--size-5) / 2);
1446
+ }
1447
+
1448
+ .table-wrap:not(:focus-within) .selection-button {
1449
+ opacity: 0;
1450
+ pointer-events: none;
1451
+ }
1452
+
1453
+ .flash.cell-selected {
1454
+ animation: flash-color 700ms ease-out;
1455
+ }
1456
+
1457
+ @keyframes flash-color {
1458
+ 0%,
1459
+ 30% {
1460
+ background: var(--color-accent-copied);
1461
+ }
1462
+
1463
+ 100% {
1464
+ background: transparent;
1465
+ }
1466
+ }
1467
+
1468
+ .frozen-column {
1469
+ position: sticky;
1470
+ z-index: var(--layer-2);
1471
+ border-right: 1px solid var(--border-color-primary);
1472
+ }
1473
+
1474
+ tr:nth-child(odd) .frozen-column {
1475
+ background: var(--table-odd-background-fill);
1476
+ }
1477
+
1478
+ tr:nth-child(even) .frozen-column {
1479
+ background: var(--table-even-background-fill);
1480
+ }
1481
+
1482
+ .always-frozen {
1483
+ z-index: var(--layer-3);
1484
+ }
1243
1485
  </style>