@gradio/dataframe 0.14.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 (44) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/Dataframe.stories.svelte +283 -7
  3. package/Index.svelte +22 -3
  4. package/dist/Index.svelte +18 -4
  5. package/dist/Index.svelte.d.ts +16 -0
  6. package/dist/shared/EditableCell.svelte +49 -7
  7. package/dist/shared/EditableCell.svelte.d.ts +1 -1
  8. package/dist/shared/Table.svelte +692 -411
  9. package/dist/shared/Table.svelte.d.ts +4 -0
  10. package/dist/shared/Toolbar.svelte +122 -30
  11. package/dist/shared/Toolbar.svelte.d.ts +4 -0
  12. package/dist/shared/VirtualTable.svelte +70 -26
  13. package/dist/shared/VirtualTable.svelte.d.ts +1 -0
  14. package/dist/shared/icons/FilterIcon.svelte +11 -0
  15. package/dist/shared/icons/FilterIcon.svelte.d.ts +16 -0
  16. package/dist/shared/icons/SortIcon.svelte +90 -0
  17. package/dist/shared/icons/SortIcon.svelte.d.ts +20 -0
  18. package/dist/shared/selection_utils.d.ts +30 -0
  19. package/dist/shared/selection_utils.js +139 -0
  20. package/dist/shared/types.d.ts +18 -0
  21. package/dist/shared/types.js +2 -0
  22. package/dist/shared/utils/menu_utils.d.ts +42 -0
  23. package/dist/shared/utils/menu_utils.js +58 -0
  24. package/dist/shared/utils/sort_utils.d.ts +7 -0
  25. package/dist/shared/utils/sort_utils.js +39 -0
  26. package/dist/shared/utils/table_utils.d.ts +12 -0
  27. package/dist/shared/utils/table_utils.js +148 -0
  28. package/package.json +8 -8
  29. package/shared/EditableCell.svelte +55 -7
  30. package/shared/Table.svelte +762 -478
  31. package/shared/Toolbar.svelte +125 -30
  32. package/shared/VirtualTable.svelte +73 -26
  33. package/shared/icons/FilterIcon.svelte +12 -0
  34. package/shared/icons/SortIcon.svelte +95 -0
  35. package/shared/selection_utils.ts +230 -0
  36. package/shared/types.ts +29 -0
  37. package/shared/utils/menu_utils.ts +115 -0
  38. package/shared/utils/sort_utils.test.ts +71 -0
  39. package/shared/utils/sort_utils.ts +55 -0
  40. package/shared/utils/table_utils.test.ts +114 -0
  41. package/shared/utils/table_utils.ts +206 -0
  42. package/dist/shared/table_utils.d.ts +0 -6
  43. package/dist/shared/table_utils.js +0 -27
  44. package/shared/table_utils.ts +0 -38
@@ -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,7 +6,27 @@ 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";
10
- import { copy_table_data } from "./table_utils";
9
+ import SortIcon from "./icons/SortIcon.svelte";
10
+ import {
11
+ is_cell_selected,
12
+ handle_selection,
13
+ handle_delete_key,
14
+ should_show_cell_menu,
15
+ get_next_cell_coordinates,
16
+ get_range_selection,
17
+ move_cursor,
18
+ get_current_indices,
19
+ handle_click_outside as handle_click_outside_util,
20
+ select_column,
21
+ select_row,
22
+ calculate_selection_positions
23
+ } from "./selection_utils";
24
+ import {
25
+ copy_table_data,
26
+ get_max,
27
+ handle_file_upload,
28
+ sort_table_data
29
+ } from "./utils/table_utils";
11
30
  export let datatype;
12
31
  export let label = null;
13
32
  export let show_label = true;
@@ -29,56 +48,69 @@ export let stream_handler;
29
48
  export let show_fullscreen_button = false;
30
49
  export let show_copy_button = false;
31
50
  export let value_is_output = false;
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;
57
+ let selected_cells = [];
58
+ $:
59
+ selected_cells = [...selected_cells];
32
60
  let selected = false;
33
- let clicked_cell = void 0;
61
+ $:
62
+ selected = selected_cells.length > 0 ? selected_cells[selected_cells.length - 1] : false;
34
63
  export let display_value = null;
35
64
  export let styling = null;
36
65
  let t_rect;
66
+ let els = {};
67
+ let data_binding = {};
37
68
  const dispatch = createEventDispatcher();
38
69
  let editing = false;
70
+ let clear_on_focus = false;
71
+ let header_edit = false;
72
+ let selected_header = false;
73
+ let active_cell_menu = null;
74
+ let active_header_menu = null;
75
+ let is_fullscreen = false;
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
+ });
39
87
  const get_data_at = (row, col) => data?.[row]?.[col]?.value;
40
- let last_selected = null;
41
- $: {
42
- if (selected !== false && !dequal(selected, last_selected)) {
43
- const [row, col] = selected;
44
- if (!isNaN(row) && !isNaN(col) && data[row]) {
45
- dispatch("select", {
46
- index: [row, col],
47
- value: get_data_at(row, col),
48
- row_value: data[row].map((d) => d.value)
49
- });
50
- last_selected = selected;
51
- }
52
- }
53
- }
54
- let els = {};
55
- let data_binding = {};
56
88
  function make_id() {
57
89
  return Math.random().toString(36).substring(2, 15);
58
90
  }
59
- function make_headers(_head) {
91
+ function make_headers(_head, col_count2, els2) {
60
92
  let _h = _head || [];
61
- if (col_count[1] === "fixed" && _h.length < col_count[0]) {
62
- 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}`);
63
95
  _h = _h.concat(fill);
64
96
  }
65
97
  if (!_h || _h.length === 0) {
66
- return Array(col_count[0]).fill(0).map((_, i) => {
98
+ return Array(col_count2[0]).fill(0).map((_, i) => {
67
99
  const _id = make_id();
68
- els[_id] = { cell: null, input: null };
100
+ els2[_id] = { cell: null, input: null };
69
101
  return { id: _id, value: JSON.stringify(i + 1) };
70
102
  });
71
103
  }
72
104
  return _h.map((h, i) => {
73
105
  const _id = make_id();
74
- els[_id] = { cell: null, input: null };
106
+ els2[_id] = { cell: null, input: null };
75
107
  return { id: _id, value: h ?? "" };
76
108
  });
77
109
  }
78
110
  function process_data(_values) {
79
111
  const data_row_length = _values.length;
80
- return Array(row_count[1] === "fixed" ? row_count[0] : data_row_length).fill(0).map(
81
- (_, i) => Array(
112
+ return Array(row_count[1] === "fixed" ? row_count[0] : data_row_length).fill(0).map((_, i) => {
113
+ return Array(
82
114
  col_count[1] === "fixed" ? col_count[0] : data_row_length > 0 ? _values[0].length : headers.length
83
115
  ).fill(0).map((_2, j) => {
84
116
  const id = make_id();
@@ -86,14 +118,14 @@ function process_data(_values) {
86
118
  const obj = { value: _values?.[i]?.[j] ?? "", id };
87
119
  data_binding[id] = obj;
88
120
  return obj;
89
- })
90
- );
121
+ });
122
+ });
91
123
  }
92
- let _headers = make_headers(headers);
124
+ let _headers = make_headers(headers, col_count, els);
93
125
  let old_headers = headers;
94
126
  $: {
95
127
  if (!dequal(headers, old_headers)) {
96
- _headers = make_headers(headers);
128
+ _headers = make_headers(headers, col_count, els);
97
129
  old_headers = JSON.parse(JSON.stringify(headers));
98
130
  }
99
131
  }
@@ -107,6 +139,8 @@ $:
107
139
  let previous_headers = _headers.map((h) => h.value);
108
140
  let previous_data = data.map((row) => row.map((cell) => String(cell.value)));
109
141
  async function trigger_change() {
142
+ if (current_search_query)
143
+ return;
110
144
  const current_headers = _headers.map((h) => h.value);
111
145
  const current_data = data.map(
112
146
  (row) => row.map((cell) => String(cell.value))
@@ -135,36 +169,6 @@ function get_sort_status(name, _sort, direction) {
135
169
  }
136
170
  return "none";
137
171
  }
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
172
  async function handle_keydown(event) {
169
173
  if (selected_header !== false && header_edit === false) {
170
174
  switch (event.key) {
@@ -187,6 +191,43 @@ async function handle_keydown(event) {
187
191
  break;
188
192
  }
189
193
  }
194
+ if (event.key === "Delete" || event.key === "Backspace") {
195
+ if (!editable)
196
+ return;
197
+ if (editing) {
198
+ const [row, col] = editing;
199
+ const input_el = els[data[row][col].id].input;
200
+ if (input_el && input_el.selectionStart !== input_el.selectionEnd) {
201
+ return;
202
+ }
203
+ if (event.key === "Delete" && input_el?.selectionStart !== input_el?.value.length) {
204
+ return;
205
+ }
206
+ if (event.key === "Backspace" && input_el?.selectionStart !== 0) {
207
+ return;
208
+ }
209
+ }
210
+ event.preventDefault();
211
+ if (selected_cells.length > 0) {
212
+ data = handle_delete_key(data, selected_cells);
213
+ dispatch("change", {
214
+ data: data.map((row) => row.map((cell) => cell.value)),
215
+ headers: _headers.map((h) => h.value),
216
+ metadata: null
217
+ });
218
+ if (!value_is_output) {
219
+ dispatch("input");
220
+ }
221
+ }
222
+ return;
223
+ }
224
+ if (event.key === "c" && (event.metaKey || event.ctrlKey)) {
225
+ event.preventDefault();
226
+ if (selected_cells.length > 0) {
227
+ await handle_copy();
228
+ }
229
+ return;
230
+ }
190
231
  if (!selected) {
191
232
  return;
192
233
  }
@@ -199,7 +240,27 @@ async function handle_keydown(event) {
199
240
  if (editing)
200
241
  break;
201
242
  event.preventDefault();
202
- move_cursor(event.key, [i, j]);
243
+ const next_coords = move_cursor(event.key, [i, j], data);
244
+ if (next_coords) {
245
+ if (event.shiftKey) {
246
+ selected_cells = get_range_selection(
247
+ selected_cells.length > 0 ? selected_cells[0] : [i, j],
248
+ next_coords
249
+ );
250
+ editing = false;
251
+ } else {
252
+ selected_cells = [next_coords];
253
+ if (editable) {
254
+ editing = next_coords;
255
+ clear_on_focus = false;
256
+ }
257
+ }
258
+ selected = next_coords;
259
+ } else if (next_coords === false && event.key === "ArrowUp" && i === 0) {
260
+ selected_header = j;
261
+ selected = false;
262
+ editing = false;
263
+ }
203
264
  break;
204
265
  case "Escape":
205
266
  if (!editable)
@@ -208,53 +269,45 @@ async function handle_keydown(event) {
208
269
  editing = false;
209
270
  break;
210
271
  case "Enter":
211
- if (!editable)
212
- break;
213
272
  event.preventDefault();
214
- if (event.shiftKey) {
215
- add_row(i);
216
- await tick();
217
- selected = [i + 1, j];
218
- } else {
219
- if (dequal(editing, [i, j])) {
220
- const cell_id = data[i][j].id;
221
- const input_el = els[cell_id].input;
222
- if (input_el) {
223
- data[i][j].value = input_el.value;
224
- }
225
- editing = false;
273
+ if (editable) {
274
+ if (event.shiftKey) {
275
+ add_row(i);
226
276
  await tick();
227
- selected = [i, j];
277
+ selected = [i + 1, j];
228
278
  } else {
229
- editing = [i, j];
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
+ }
230
292
  }
231
293
  }
232
294
  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
295
  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
- }
296
+ event.preventDefault();
257
297
  editing = false;
298
+ const next_cell = get_next_cell_coordinates(
299
+ [i, j],
300
+ data,
301
+ event.shiftKey
302
+ );
303
+ if (next_cell) {
304
+ selected_cells = [next_cell];
305
+ selected = next_cell;
306
+ if (editable) {
307
+ editing = next_cell;
308
+ clear_on_focus = false;
309
+ }
310
+ }
258
311
  break;
259
312
  default:
260
313
  if (!editable)
@@ -267,28 +320,26 @@ async function handle_keydown(event) {
267
320
  }
268
321
  let sort_direction;
269
322
  let sort_by;
270
- function handle_sort(col) {
323
+ function handle_sort(col, direction) {
271
324
  if (typeof sort_by !== "number" || sort_by !== col) {
272
- sort_direction = "asc";
325
+ sort_direction = direction;
273
326
  sort_by = col;
274
- } else {
275
- if (sort_direction === "asc") {
276
- sort_direction = "des";
277
- } else if (sort_direction === "des") {
278
- 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;
279
333
  }
280
334
  }
281
335
  }
282
- let header_edit;
283
- let select_on_focus = false;
284
- let selected_header = false;
285
336
  async function edit_header(i, _select = false) {
286
337
  if (!editable || col_count[1] !== "dynamic" || header_edit === i)
287
338
  return;
288
339
  selected = false;
340
+ selected_cells = [];
289
341
  selected_header = i;
290
342
  header_edit = i;
291
- select_on_focus = _select;
292
343
  }
293
344
  function end_header_edit(event) {
294
345
  if (!editable)
@@ -349,78 +400,14 @@ async function add_col(index) {
349
400
  });
350
401
  }
351
402
  function handle_click_outside(event) {
352
- if (active_cell_menu && !event.target.closest(".cell-menu") || active_header_menu && !event.target.closest(".cell-menu")) {
403
+ if (handle_click_outside_util(event, parent)) {
404
+ editing = false;
405
+ selected_cells = [];
406
+ header_edit = false;
407
+ selected_header = false;
353
408
  active_cell_menu = null;
354
409
  active_header_menu = null;
355
410
  }
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
411
  }
425
412
  $:
426
413
  max = get_max(data);
@@ -430,53 +417,39 @@ let cells = [];
430
417
  let parent;
431
418
  let table;
432
419
  function set_cell_widths() {
433
- const widths = cells.map((el, i) => {
434
- return el?.clientWidth || 0;
435
- });
420
+ const widths = cells.map((el) => el?.clientWidth || 0);
436
421
  if (widths.length === 0)
437
422
  return;
438
- for (let i = 0; i < widths.length; i++) {
439
- parent.style.setProperty(
440
- `--cell-width-${i}`,
441
- `${widths[i] - scrollbar_width / widths.length}px`
442
- );
423
+ if (show_row_numbers) {
424
+ parent.style.setProperty(`--cell-width-row-number`, `${widths[0]}px`);
443
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})`;
444
438
  }
445
439
  let table_height = values.slice(0, max_height / values.length * 37).length * 37 + 37;
446
440
  let scrollbar_width = 0;
447
441
  function sort_data(_data, _display_value, _styling, col, dir) {
448
442
  let id = null;
449
- if (selected && selected[0] in data && selected[1] in data[selected[0]]) {
450
- 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;
451
445
  }
452
446
  if (typeof col !== "number" || !dir) {
453
447
  return;
454
448
  }
455
- const indices = [...Array(_data.length).keys()];
456
- if (dir === "asc") {
457
- indices.sort(
458
- (i, j) => _data[i][col].value < _data[j][col].value ? -1 : 1
459
- );
460
- } else if (dir === "des") {
461
- indices.sort(
462
- (i, j) => _data[i][col].value > _data[j][col].value ? -1 : 1
463
- );
464
- } else {
465
- return;
466
- }
467
- const temp_data = [..._data];
468
- const temp_display_value = _display_value ? [..._display_value] : null;
469
- const temp_styling = _styling ? [..._styling] : null;
470
- indices.forEach((originalIndex, sortedIndex) => {
471
- _data[sortedIndex] = temp_data[originalIndex];
472
- if (_display_value && temp_display_value)
473
- _display_value[sortedIndex] = temp_display_value[originalIndex];
474
- if (_styling && temp_styling)
475
- _styling[sortedIndex] = temp_styling[originalIndex];
476
- });
449
+ sort_table_data(_data, _display_value, _styling, col, dir);
477
450
  data = data;
478
451
  if (id) {
479
- const [i, j] = get_current_indices(id);
452
+ const [i, j] = get_current_indices(id, data);
480
453
  selected = [i, j];
481
454
  }
482
455
  }
@@ -486,7 +459,7 @@ $:
486
459
  selected_index = !!selected && selected[0];
487
460
  let is_visible = false;
488
461
  onMount(() => {
489
- const observer = new IntersectionObserver((entries, observer2) => {
462
+ const observer = new IntersectionObserver((entries) => {
490
463
  entries.forEach((entry) => {
491
464
  if (entry.isIntersecting && !is_visible) {
492
465
  set_cell_widths();
@@ -509,8 +482,42 @@ onMount(() => {
509
482
  );
510
483
  };
511
484
  });
512
- let highlighted_column = null;
513
- let active_cell_menu = null;
485
+ function handle_cell_click(event, row, col) {
486
+ if (event.target instanceof HTMLAnchorElement) {
487
+ return;
488
+ }
489
+ event.preventDefault();
490
+ event.stopPropagation();
491
+ if (show_row_numbers && col === -1)
492
+ return;
493
+ clear_on_focus = false;
494
+ active_cell_menu = null;
495
+ active_header_menu = null;
496
+ selected_header = false;
497
+ header_edit = false;
498
+ selected_cells = handle_selection([row, col], selected_cells, event);
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
+ }
513
+ }
514
+ toggle_cell_button(row, col);
515
+ dispatch("select", {
516
+ index: [row, col],
517
+ value: get_data_at(row, col),
518
+ row_value: data[row].map((d) => d.value)
519
+ });
520
+ }
514
521
  function toggle_cell_menu(event, row, col) {
515
522
  event.stopPropagation();
516
523
  if (active_cell_menu && active_cell_menu.row === row && active_cell_menu.col === col) {
@@ -519,12 +526,7 @@ function toggle_cell_menu(event, row, col) {
519
526
  const cell = event.target.closest("td");
520
527
  if (cell) {
521
528
  const rect = cell.getBoundingClientRect();
522
- active_cell_menu = {
523
- row,
524
- col,
525
- x: rect.right,
526
- y: rect.bottom
527
- };
529
+ active_cell_menu = { row, col, x: rect.right, y: rect.bottom };
528
530
  }
529
531
  }
530
532
  }
@@ -543,25 +545,18 @@ function add_col_at(index, position) {
543
545
  function handle_resize() {
544
546
  active_cell_menu = null;
545
547
  active_header_menu = null;
548
+ selected_cells = [];
549
+ selected = false;
550
+ editing = false;
546
551
  set_cell_widths();
547
552
  }
548
553
  let active_button = null;
549
554
  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
- }
555
+ active_button = active_button?.type === "header" && active_button.col === col ? null : { type: "header", col };
555
556
  }
556
557
  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
- }
558
+ active_button = active_button?.type === "cell" && active_button.row === row && active_button.col === col ? null : { type: "cell", row, col };
562
559
  }
563
- let active_header_menu = null;
564
- let is_fullscreen = false;
565
560
  function toggle_fullscreen() {
566
561
  if (!document.fullscreenElement) {
567
562
  parent.requestFullscreen();
@@ -575,7 +570,11 @@ function handle_fullscreen_change() {
575
570
  is_fullscreen = !!document.fullscreenElement;
576
571
  }
577
572
  async function handle_copy() {
578
- await copy_table_data(data, _headers);
573
+ await copy_table_data(data, selected_cells);
574
+ copy_flash = true;
575
+ setTimeout(() => {
576
+ copy_flash = false;
577
+ }, 800);
579
578
  }
580
579
  function toggle_header_menu(event, col) {
581
580
  event.stopPropagation();
@@ -585,11 +584,7 @@ function toggle_header_menu(event, col) {
585
584
  const header = event.target.closest("th");
586
585
  if (header) {
587
586
  const rect = header.getBoundingClientRect();
588
- active_header_menu = {
589
- col,
590
- x: rect.right,
591
- y: rect.bottom
592
- };
587
+ active_header_menu = { col, x: rect.right, y: rect.bottom };
593
588
  }
594
589
  }
595
590
  }
@@ -630,6 +625,78 @@ function delete_col_at(index) {
630
625
  active_cell_menu = null;
631
626
  active_header_menu = null;
632
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
+ }
633
700
  </script>
634
701
 
635
702
  <svelte:window on:resize={() => set_cell_widths()} />
@@ -647,6 +714,10 @@ function delete_col_at(index) {
647
714
  on:click={toggle_fullscreen}
648
715
  on_copy={handle_copy}
649
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}
650
721
  />
651
722
  </div>
652
723
  <div
@@ -654,11 +725,28 @@ function delete_col_at(index) {
654
725
  class="table-wrap"
655
726
  class:dragging
656
727
  class:no-wrap={!wrap}
657
- style="height:{table_height}px"
728
+ style="height:{table_height}px;"
729
+ class:menu-open={active_cell_menu || active_header_menu}
658
730
  on:keydown={(e) => handle_keydown(e)}
659
731
  role="grid"
660
732
  tabindex="0"
661
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}
662
750
  <table
663
751
  bind:contentRect={t_rect}
664
752
  bind:this={table}
@@ -670,39 +758,59 @@ function delete_col_at(index) {
670
758
  <thead>
671
759
  <tr>
672
760
  {#if show_row_numbers}
673
- <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>
674
771
  {/if}
675
772
  {#each _headers as { value, id }, i (id)}
676
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}
677
778
  class:editing={header_edit === i}
678
779
  aria-sort={get_sort_status(value, sort_by, sort_direction)}
679
- 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'};"
680
794
  >
681
795
  <div class="cell-wrap">
682
- <EditableCell
683
- {value}
684
- {latex_delimiters}
685
- {line_breaks}
686
- header
687
- edit={false}
688
- el={null}
689
- {root}
690
- />
691
-
692
- <div
693
- class:sorted={sort_by === i}
694
- class:des={sort_by === i && sort_direction === "des"}
695
- class="sort-button {sort_direction} "
696
- >
697
- <svg
698
- width="1em"
699
- height="1em"
700
- viewBox="0 0 9 7"
701
- fill="none"
702
- xmlns="http://www.w3.org/2000/svg"
703
- >
704
- <path d="M4.49999 0L8.3971 6.75H0.602875L4.49999 0Z" />
705
- </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>
706
814
  </div>
707
815
  </div>
708
816
  </th>
@@ -722,6 +830,7 @@ function delete_col_at(index) {
722
830
  edit={false}
723
831
  el={null}
724
832
  {root}
833
+ {editable}
725
834
  />
726
835
  </div>
727
836
  </td>
@@ -737,8 +846,23 @@ function delete_col_at(index) {
737
846
  boundedheight={false}
738
847
  disable_click={true}
739
848
  {root}
740
- on:load={(e) => blob_to_string(data_uri_to_blob(e.detail.data))}
849
+ on:load={({ detail }) =>
850
+ handle_file_upload(
851
+ detail.data,
852
+ (head) => {
853
+ _headers = make_headers(
854
+ head.map((h) => h ?? ""),
855
+ col_count,
856
+ els
857
+ );
858
+ return _headers;
859
+ },
860
+ (vals) => {
861
+ values = vals;
862
+ }
863
+ )}
741
864
  bind:dragging
865
+ aria_label={i18n("dataframe.drop_to_upload")}
742
866
  >
743
867
  <VirtualTable
744
868
  bind:items={data}
@@ -746,19 +870,44 @@ function delete_col_at(index) {
746
870
  bind:actual_height={table_height}
747
871
  bind:table_scrollbar_width={scrollbar_width}
748
872
  selected={selected_index}
873
+ disable_scroll={active_cell_menu !== null ||
874
+ active_header_menu !== null}
749
875
  >
750
876
  {#if label && label.length !== 0}
751
877
  <caption class="sr-only">{label}</caption>
752
878
  {/if}
753
879
  <tr slot="thead">
754
880
  {#if show_row_numbers}
755
- <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>
756
891
  {/if}
757
892
  {#each _headers as { value, id }, i (id)}
758
893
  <th
894
+ class:frozen-column={i < actual_pinned_columns}
895
+ class:last-frozen={i === actual_pinned_columns - 1}
759
896
  class:focus={header_edit === i || selected_header === i}
760
897
  aria-sort={get_sort_status(value, sort_by, sort_direction)}
761
- 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'};"
762
911
  on:click={() => {
763
912
  toggle_header_button(i);
764
913
  }}
@@ -766,6 +915,7 @@ function delete_col_at(index) {
766
915
  <div class="cell-wrap">
767
916
  <div class="header-content">
768
917
  <EditableCell
918
+ {max_chars}
769
919
  bind:value={_headers[i].value}
770
920
  bind:el={els[id].input}
771
921
  {latex_delimiters}
@@ -775,84 +925,76 @@ function delete_col_at(index) {
775
925
  on:dblclick={() => edit_header(i)}
776
926
  header
777
927
  {root}
928
+ {editable}
778
929
  />
779
- <button
780
- class:sorted={sort_by === i}
781
- class:des={sort_by === i && sort_direction === "des"}
782
- class="sort-button {sort_direction}"
783
- tabindex="0"
784
- on:click={(event) => {
785
- event.stopPropagation();
786
- handle_sort(i);
787
- }}
788
- >
789
- <svg
790
- width="1em"
791
- height="1em"
792
- viewBox="0 0 9 7"
793
- fill="none"
794
- xmlns="http://www.w3.org/2000/svg"
795
- >
796
- <path d="M4.49999 0L8.3971 6.75H0.602875L4.49999 0Z" />
797
- </svg>
798
- </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>
799
937
  </div>
800
-
801
938
  {#if editable}
802
939
  <button
803
940
  class="cell-menu-button"
804
941
  on:click={(event) => toggle_header_menu(event, i)}
805
942
  >
806
-
943
+ &#8942;
807
944
  </button>
808
945
  {/if}
809
946
  </div>
810
947
  </th>
811
948
  {/each}
812
949
  </tr>
813
-
814
950
  <tr slot="tbody" let:item let:index class:row_odd={index % 2 === 0}>
815
951
  {#if show_row_numbers}
816
- <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>
817
959
  {/if}
818
960
  {#each item as { value, id }, j (id)}
819
961
  <td
820
- 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}
821
966
  on:touchstart={(event) => {
822
- event.preventDefault();
823
- event.stopPropagation();
824
- clear_on_focus = false;
825
- clicked_cell = { row: index, col: j };
826
- selected = [index, j];
827
- selected_header = false;
828
- header_edit = false;
829
- if (editable) {
830
- editing = [index, j];
831
- }
832
- toggle_cell_button(index, j);
967
+ const touch = event.touches[0];
968
+ const mouseEvent = new MouseEvent("click", {
969
+ clientX: touch.clientX,
970
+ clientY: touch.clientY,
971
+ bubbles: true,
972
+ cancelable: true,
973
+ view: window
974
+ });
975
+ handle_cell_click(mouseEvent, index, j);
833
976
  }}
834
977
  on:mousedown={(event) => {
835
978
  event.preventDefault();
836
979
  event.stopPropagation();
837
980
  }}
838
- on:click={(event) => {
839
- event.preventDefault();
840
- event.stopPropagation();
841
- clear_on_focus = false;
842
- active_cell_menu = null;
843
- active_header_menu = null;
844
- clicked_cell = { row: index, col: j };
845
- selected = [index, j];
846
- selected_header = false;
847
- header_edit = false;
848
- if (editable) {
849
- editing = [index, j];
850
- }
851
- toggle_cell_button(index, j);
852
- }}
853
- style:width="var(--cell-width-{j})"
854
- style={styling?.[index]?.[j] || ""}
855
- class:focus={dequal(selected, [index, j])}
981
+ on:click={(event) => handle_cell_click(event, 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)}
997
+ class={is_cell_selected([index, j], selected_cells)}
856
998
  class:menu-active={active_cell_menu &&
857
999
  active_cell_menu.row === index &&
858
1000
  active_cell_menu.col === j}
@@ -871,15 +1013,25 @@ function delete_col_at(index) {
871
1013
  clear_on_focus = false;
872
1014
  parent.focus();
873
1015
  }}
1016
+ on:focus={() => {
1017
+ const row = index;
1018
+ const col = j;
1019
+ if (
1020
+ !selected_cells.some(([r, c]) => r === row && c === col)
1021
+ ) {
1022
+ selected_cells = [[row, col]];
1023
+ }
1024
+ }}
874
1025
  {clear_on_focus}
875
1026
  {root}
1027
+ {max_chars}
876
1028
  />
877
- {#if editable}
1029
+ {#if editable && should_show_cell_menu([index, j], selected_cells, editable)}
878
1030
  <button
879
1031
  class="cell-menu-button"
880
1032
  on:click={(event) => toggle_cell_menu(event, index, j)}
881
1033
  >
882
-
1034
+ &#8942;
883
1035
  </button>
884
1036
  {/if}
885
1037
  </div>
@@ -931,15 +1083,6 @@ function delete_col_at(index) {
931
1083
  {/if}
932
1084
 
933
1085
  <style>
934
- .button-wrap:hover svg {
935
- color: var(--color-accent);
936
- }
937
-
938
- .button-wrap svg {
939
- margin-right: var(--size-1);
940
- margin-left: -5px;
941
- }
942
-
943
1086
  .label p {
944
1087
  position: relative;
945
1088
  z-index: var(--layer-4);
@@ -948,17 +1091,25 @@ function delete_col_at(index) {
948
1091
  font-size: var(--block-label-text-size);
949
1092
  }
950
1093
 
1094
+ .table-container {
1095
+ display: flex;
1096
+ flex-direction: column;
1097
+ gap: var(--size-2);
1098
+ }
1099
+
951
1100
  .table-wrap {
952
1101
  position: relative;
953
1102
  transition: 150ms;
954
1103
  border: 1px solid var(--border-color-primary);
955
1104
  border-radius: var(--table-radius);
1105
+ }
1106
+
1107
+ .table-wrap.menu-open {
956
1108
  overflow: hidden;
957
1109
  }
958
1110
 
959
1111
  .table-wrap:focus-within {
960
1112
  outline: none;
961
- background-color: none;
962
1113
  }
963
1114
 
964
1115
  .dragging {
@@ -980,6 +1131,7 @@ function delete_col_at(index) {
980
1131
  line-height: var(--line-md);
981
1132
  font-family: var(--font-mono);
982
1133
  border-spacing: 0;
1134
+ border-collapse: separate;
983
1135
  }
984
1136
 
985
1137
  div:not(.no-wrap) td {
@@ -997,8 +1149,7 @@ function delete_col_at(index) {
997
1149
  thead {
998
1150
  position: sticky;
999
1151
  top: 0;
1000
- left: 0;
1001
- z-index: var(--layer-1);
1152
+ z-index: var(--layer-2);
1002
1153
  box-shadow: var(--shadow-drop);
1003
1154
  }
1004
1155
 
@@ -1034,6 +1185,12 @@ function delete_col_at(index) {
1034
1185
  th.focus,
1035
1186
  td.focus {
1036
1187
  --ring-color: var(--color-accent);
1188
+ box-shadow: inset 0 0 0 2px var(--ring-color);
1189
+ z-index: var(--layer-1);
1190
+ }
1191
+
1192
+ th.focus {
1193
+ z-index: var(--layer-2);
1037
1194
  }
1038
1195
 
1039
1196
  tr:last-child td:first-child {
@@ -1048,33 +1205,10 @@ function delete_col_at(index) {
1048
1205
  background: var(--table-even-background-fill);
1049
1206
  }
1050
1207
 
1051
- th svg {
1052
- fill: currentColor;
1053
- font-size: 10px;
1054
- }
1055
-
1056
- .sort-button {
1208
+ .sort-buttons {
1057
1209
  display: flex;
1058
- flex: none;
1059
- justify-content: center;
1060
1210
  align-items: center;
1061
- transition: 150ms;
1062
- cursor: pointer;
1063
- padding: var(--size-2);
1064
- color: var(--body-text-color-subdued);
1065
- line-height: var(--text-sm);
1066
- }
1067
-
1068
- .sort-button:hover {
1069
- color: var(--body-text-color);
1070
- }
1071
-
1072
- .des {
1073
- transform: scaleY(-1);
1074
- }
1075
-
1076
- .sort-button.sorted {
1077
- color: var(--color-accent);
1211
+ flex-shrink: 0;
1078
1212
  }
1079
1213
 
1080
1214
  .editing {
@@ -1083,25 +1217,26 @@ function delete_col_at(index) {
1083
1217
 
1084
1218
  .cell-wrap {
1085
1219
  display: flex;
1086
- align-items: center;
1220
+ align-items: flex-start;
1087
1221
  outline: none;
1088
- height: var(--size-full);
1089
1222
  min-height: var(--size-9);
1090
- overflow: hidden;
1223
+ position: relative;
1224
+ height: auto;
1091
1225
  }
1092
1226
 
1093
1227
  .header-content {
1094
1228
  display: flex;
1095
1229
  align-items: center;
1230
+ justify-content: space-between;
1096
1231
  overflow: hidden;
1097
1232
  flex-grow: 1;
1098
1233
  min-width: 0;
1099
- }
1100
-
1101
- .controls-wrap {
1102
- display: flex;
1103
- justify-content: flex-end;
1104
- padding-top: var(--size-2);
1234
+ white-space: normal;
1235
+ overflow-wrap: break-word;
1236
+ word-break: normal;
1237
+ height: 100%;
1238
+ padding: var(--size-1);
1239
+ gap: var(--size-1);
1105
1240
  }
1106
1241
 
1107
1242
  .row_odd {
@@ -1112,10 +1247,6 @@ function delete_col_at(index) {
1112
1247
  background: var(--background-fill-primary);
1113
1248
  }
1114
1249
 
1115
- table {
1116
- border-collapse: separate;
1117
- }
1118
-
1119
1250
  .cell-menu-button {
1120
1251
  flex-shrink: 0;
1121
1252
  display: none;
@@ -1128,77 +1259,227 @@ function delete_col_at(index) {
1128
1259
  padding: 0;
1129
1260
  margin-right: var(--spacing-sm);
1130
1261
  z-index: var(--layer-1);
1262
+ position: absolute;
1263
+ right: var(--size-1);
1264
+ top: 50%;
1265
+ transform: translateY(-50%);
1131
1266
  }
1132
1267
 
1133
- .cell-menu-button:hover {
1134
- background-color: var(--color-bg-hover);
1268
+ .cell-selected .cell-menu-button {
1269
+ display: flex;
1270
+ align-items: center;
1271
+ justify-content: center;
1135
1272
  }
1136
1273
 
1137
- td.focus .cell-menu-button {
1274
+ .header-row {
1138
1275
  display: flex;
1276
+ justify-content: flex-end;
1139
1277
  align-items: center;
1140
- justify-content: center;
1278
+ gap: var(--size-2);
1279
+ min-height: var(--size-6);
1280
+ flex-wrap: nowrap;
1281
+ width: 100%;
1141
1282
  }
1142
1283
 
1143
- th .header-content {
1144
- white-space: normal;
1145
- overflow-wrap: break-word;
1146
- word-break: break-word;
1284
+ .label {
1285
+ flex: 1 1 auto;
1286
+ margin-right: auto;
1147
1287
  }
1148
1288
 
1149
- .table-container {
1150
- display: flex;
1151
- flex-direction: column;
1152
- gap: var(--size-2);
1289
+ .label p {
1290
+ margin: 0;
1291
+ color: var(--block-label-text-color);
1292
+ font-size: var(--block-label-text-size);
1293
+ line-height: var(--line-sm);
1294
+ }
1295
+
1296
+ .toolbar {
1297
+ flex: 0 0 auto;
1153
1298
  }
1154
1299
 
1155
1300
  .row-number,
1156
1301
  .row-number-header {
1157
- width: var(--size-7);
1158
- min-width: var(--size-7);
1159
1302
  text-align: center;
1160
1303
  background: var(--table-even-background-fill);
1161
- position: sticky;
1162
- left: 0;
1163
1304
  font-size: var(--input-text-size);
1164
1305
  color: var(--body-text-color);
1165
- padding: var(--size-1) var(--size-2);
1306
+ padding: var(--size-1);
1307
+ min-width: var(--size-12);
1308
+ width: var(--size-12);
1166
1309
  overflow: hidden;
1167
1310
  text-overflow: ellipsis;
1168
1311
  white-space: nowrap;
1169
1312
  font-weight: var(--weight-semibold);
1170
1313
  }
1171
1314
 
1172
- .row-number-header {
1173
- 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;
1174
1321
  }
1175
1322
 
1176
- .row-number {
1177
- z-index: var(--layer-1);
1323
+ .row-number-header :global(.sort-icons) {
1324
+ margin-right: 0;
1178
1325
  }
1179
1326
 
1180
1327
  :global(tbody > tr:nth-child(odd)) .row-number {
1181
1328
  background: var(--table-odd-background-fill);
1182
1329
  }
1183
1330
 
1184
- .header-row {
1331
+ .cell-selected {
1332
+ --ring-color: var(--color-accent);
1333
+ box-shadow: inset 0 0 0 2px var(--ring-color);
1334
+ z-index: var(--layer-1);
1335
+ position: relative;
1336
+ }
1337
+
1338
+ .cell-selected.no-top {
1339
+ box-shadow:
1340
+ inset 2px 0 0 var(--ring-color),
1341
+ inset -2px 0 0 var(--ring-color),
1342
+ inset 0 -2px 0 var(--ring-color);
1343
+ }
1344
+
1345
+ .cell-selected.no-bottom {
1346
+ box-shadow:
1347
+ inset 2px 0 0 var(--ring-color),
1348
+ inset -2px 0 0 var(--ring-color),
1349
+ inset 0 2px 0 var(--ring-color);
1350
+ }
1351
+
1352
+ .cell-selected.no-left {
1353
+ box-shadow:
1354
+ inset 0 2px 0 var(--ring-color),
1355
+ inset -2px 0 0 var(--ring-color),
1356
+ inset 0 -2px 0 var(--ring-color);
1357
+ }
1358
+
1359
+ .cell-selected.no-right {
1360
+ box-shadow:
1361
+ inset 0 2px 0 var(--ring-color),
1362
+ inset 2px 0 0 var(--ring-color),
1363
+ inset 0 -2px 0 var(--ring-color);
1364
+ }
1365
+
1366
+ .cell-selected.no-top.no-left {
1367
+ box-shadow:
1368
+ inset -2px 0 0 var(--ring-color),
1369
+ inset 0 -2px 0 var(--ring-color);
1370
+ }
1371
+
1372
+ .cell-selected.no-top.no-right {
1373
+ box-shadow:
1374
+ inset 2px 0 0 var(--ring-color),
1375
+ inset 0 -2px 0 var(--ring-color);
1376
+ }
1377
+
1378
+ .cell-selected.no-bottom.no-left {
1379
+ box-shadow:
1380
+ inset -2px 0 0 var(--ring-color),
1381
+ inset 0 2px 0 var(--ring-color);
1382
+ }
1383
+
1384
+ .cell-selected.no-bottom.no-right {
1385
+ box-shadow:
1386
+ inset 2px 0 0 var(--ring-color),
1387
+ inset 0 2px 0 var(--ring-color);
1388
+ }
1389
+
1390
+ .cell-selected.no-top.no-bottom {
1391
+ box-shadow:
1392
+ inset 2px 0 0 var(--ring-color),
1393
+ inset -2px 0 0 var(--ring-color);
1394
+ }
1395
+
1396
+ .cell-selected.no-left.no-right {
1397
+ box-shadow:
1398
+ inset 0 2px 0 var(--ring-color),
1399
+ inset 0 -2px 0 var(--ring-color);
1400
+ }
1401
+
1402
+ .cell-selected.no-top.no-left.no-right {
1403
+ box-shadow: inset 0 -2px 0 var(--ring-color);
1404
+ }
1405
+
1406
+ .cell-selected.no-bottom.no-left.no-right {
1407
+ box-shadow: inset 0 2px 0 var(--ring-color);
1408
+ }
1409
+
1410
+ .cell-selected.no-left.no-top.no-bottom {
1411
+ box-shadow: inset -2px 0 0 var(--ring-color);
1412
+ }
1413
+
1414
+ .cell-selected.no-right.no-top.no-bottom {
1415
+ box-shadow: inset 2px 0 0 var(--ring-color);
1416
+ }
1417
+
1418
+ .cell-selected.no-top.no-bottom.no-left.no-right {
1419
+ box-shadow: none;
1420
+ }
1421
+
1422
+ .selection-button {
1423
+ position: absolute;
1185
1424
  display: flex;
1186
- justify-content: space-between;
1187
1425
  align-items: center;
1188
- gap: var(--size-2);
1189
- height: var(--size-6);
1190
- min-height: var(--size-6);
1426
+ justify-content: center;
1427
+ background: var(--color-accent);
1428
+ color: white;
1429
+ border-radius: var(--radius-sm);
1430
+ z-index: var(--layer-4);
1191
1431
  }
1192
1432
 
1193
- .label {
1194
- flex: 1;
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);
1195
1439
  }
1196
1440
 
1197
- .label p {
1198
- position: relative;
1199
- z-index: var(--layer-4);
1200
- margin: 0;
1201
- color: var(--block-label-text-color);
1202
- font-size: var(--block-label-text-size);
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);
1203
1484
  }
1204
1485
  </style>