@gradio/dataframe 0.16.4 → 0.17.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 (87) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/Dataframe.stories.svelte +202 -9
  3. package/Index.svelte +7 -13
  4. package/dist/Index.svelte +5 -9
  5. package/dist/Index.svelte.d.ts +9 -2
  6. package/dist/shared/CellMenu.svelte +53 -1
  7. package/dist/shared/CellMenu.svelte.d.ts +5 -0
  8. package/dist/shared/CellMenuButton.svelte +44 -0
  9. package/dist/shared/CellMenuButton.svelte.d.ts +16 -0
  10. package/dist/shared/CellMenuIcons.svelte +79 -0
  11. package/dist/shared/EditableCell.svelte +83 -14
  12. package/dist/shared/EditableCell.svelte.d.ts +12 -3
  13. package/dist/shared/EmptyRowButton.svelte +28 -0
  14. package/dist/shared/EmptyRowButton.svelte.d.ts +16 -0
  15. package/dist/shared/RowNumber.svelte +40 -0
  16. package/dist/shared/RowNumber.svelte.d.ts +17 -0
  17. package/dist/shared/Table.svelte +543 -1110
  18. package/dist/shared/Table.svelte.d.ts +4 -0
  19. package/dist/shared/TableCell.svelte +291 -0
  20. package/dist/shared/TableCell.svelte.d.ts +57 -0
  21. package/dist/shared/TableHeader.svelte +239 -0
  22. package/dist/shared/TableHeader.svelte.d.ts +45 -0
  23. package/dist/shared/Toolbar.svelte +18 -8
  24. package/dist/shared/VirtualTable.svelte +66 -19
  25. package/dist/shared/VirtualTable.svelte.d.ts +4 -0
  26. package/dist/shared/context/keyboard_context.d.ts +37 -0
  27. package/dist/shared/context/keyboard_context.js +12 -0
  28. package/dist/shared/context/selection_context.d.ts +32 -0
  29. package/dist/shared/context/selection_context.js +107 -0
  30. package/dist/shared/context/table_context.d.ts +141 -0
  31. package/dist/shared/context/table_context.js +375 -0
  32. package/dist/shared/icons/Padlock.svelte +24 -0
  33. package/dist/shared/icons/Padlock.svelte.d.ts +23 -0
  34. package/dist/shared/icons/SelectionButtons.svelte +85 -0
  35. package/dist/shared/icons/SelectionButtons.svelte.d.ts +18 -0
  36. package/dist/shared/icons/SortArrowDown.svelte +24 -0
  37. package/dist/shared/icons/SortArrowDown.svelte.d.ts +16 -0
  38. package/dist/shared/icons/SortArrowUp.svelte +24 -0
  39. package/dist/shared/icons/SortArrowUp.svelte.d.ts +16 -0
  40. package/dist/shared/icons/SortButtonDown.svelte +14 -0
  41. package/dist/shared/icons/SortButtonDown.svelte.d.ts +23 -0
  42. package/dist/shared/icons/SortButtonUp.svelte +15 -0
  43. package/dist/shared/icons/SortButtonUp.svelte.d.ts +23 -0
  44. package/dist/shared/icons/SortIcon.svelte +46 -68
  45. package/dist/shared/icons/SortIcon.svelte.d.ts +3 -2
  46. package/dist/shared/selection_utils.d.ts +2 -1
  47. package/dist/shared/selection_utils.js +39 -10
  48. package/dist/shared/utils/data_processing.d.ts +13 -0
  49. package/dist/shared/utils/data_processing.js +45 -0
  50. package/dist/shared/utils/drag_utils.d.ts +15 -0
  51. package/dist/shared/utils/drag_utils.js +57 -0
  52. package/dist/shared/utils/keyboard_utils.d.ts +2 -0
  53. package/dist/shared/utils/keyboard_utils.js +186 -0
  54. package/dist/shared/utils/sort_utils.d.ts +9 -3
  55. package/dist/shared/utils/sort_utils.js +30 -24
  56. package/dist/shared/utils/table_utils.d.ts +6 -5
  57. package/dist/shared/utils/table_utils.js +13 -56
  58. package/package.json +9 -9
  59. package/shared/CellMenu.svelte +52 -1
  60. package/shared/CellMenuButton.svelte +45 -0
  61. package/shared/CellMenuIcons.svelte +79 -0
  62. package/shared/EditableCell.svelte +97 -18
  63. package/shared/EmptyRowButton.svelte +29 -0
  64. package/shared/RowNumber.svelte +41 -0
  65. package/shared/Table.svelte +568 -1223
  66. package/shared/TableCell.svelte +324 -0
  67. package/shared/TableHeader.svelte +256 -0
  68. package/shared/Toolbar.svelte +19 -8
  69. package/shared/VirtualTable.svelte +72 -19
  70. package/shared/context/keyboard_context.ts +65 -0
  71. package/shared/context/selection_context.ts +168 -0
  72. package/shared/context/table_context.ts +625 -0
  73. package/shared/icons/Padlock.svelte +24 -0
  74. package/shared/icons/SelectionButtons.svelte +93 -0
  75. package/shared/icons/SortArrowDown.svelte +25 -0
  76. package/shared/icons/SortArrowUp.svelte +25 -0
  77. package/shared/icons/SortButtonDown.svelte +14 -0
  78. package/shared/icons/SortButtonUp.svelte +15 -0
  79. package/shared/icons/SortIcon.svelte +47 -70
  80. package/shared/selection_utils.ts +39 -13
  81. package/shared/utils/data_processing.ts +72 -0
  82. package/shared/utils/drag_utils.ts +92 -0
  83. package/shared/utils/keyboard_utils.ts +238 -0
  84. package/shared/utils/sort_utils.test.ts +262 -14
  85. package/shared/utils/sort_utils.ts +39 -31
  86. package/shared/utils/table_utils.test.ts +66 -45
  87. package/shared/utils/table_utils.ts +16 -86
@@ -1,24 +1,30 @@
1
+ <script context="module">import {
2
+ create_dataframe_context
3
+ } from "./context/table_context";
4
+ import { create_keyboard_context } from "./context/keyboard_context";
5
+ import { create_selection_context } from "./context/selection_context";
6
+ </script>
7
+
1
8
  <script>import { afterUpdate, createEventDispatcher, tick, onMount } from "svelte";
2
9
  import { dequal } from "dequal/lite";
3
10
  import { Upload } from "@gradio/upload";
4
11
  import EditableCell from "./EditableCell.svelte";
12
+ import RowNumber from "./RowNumber.svelte";
13
+ import TableHeader from "./TableHeader.svelte";
14
+ import TableCell from "./TableCell.svelte";
15
+ import EmptyRowButton from "./EmptyRowButton.svelte";
5
16
  import {} from "@gradio/client";
6
17
  import VirtualTable from "./VirtualTable.svelte";
7
18
  import CellMenu from "./CellMenu.svelte";
8
19
  import Toolbar from "./Toolbar.svelte";
9
- import SortIcon from "./icons/SortIcon.svelte";
10
20
  import {
11
21
  is_cell_selected,
12
- handle_selection,
13
- handle_delete_key,
14
22
  should_show_cell_menu,
15
23
  get_next_cell_coordinates,
16
24
  get_range_selection,
17
25
  move_cursor,
18
26
  get_current_indices,
19
27
  handle_click_outside as handle_click_outside_util,
20
- select_column,
21
- select_row,
22
28
  calculate_selection_positions
23
29
  } from "./selection_utils";
24
30
  import {
@@ -27,6 +33,11 @@ import {
27
33
  handle_file_upload,
28
34
  sort_table_data
29
35
  } from "./utils/table_utils";
36
+ import { make_headers, process_data } from "./utils/data_processing";
37
+ import { handle_keydown } from "./utils/keyboard_utils";
38
+ import {
39
+ create_drag_handlers
40
+ } from "./utils/drag_utils";
30
41
  export let datatype;
31
42
  export let label = null;
32
43
  export let show_label = true;
@@ -35,6 +46,7 @@ export let values = [];
35
46
  export let col_count;
36
47
  export let row_count;
37
48
  export let latex_delimiters;
49
+ export let components = {};
38
50
  export let editable = true;
39
51
  export let wrap = false;
40
52
  export let root;
@@ -51,27 +63,50 @@ export let value_is_output = false;
51
63
  export let max_chars = void 0;
52
64
  export let show_search = "none";
53
65
  export let pinned_columns = 0;
54
- let actual_pinned_columns = 0;
66
+ export let static_columns = [];
55
67
  $:
56
68
  actual_pinned_columns = pinned_columns && data?.[0]?.length ? Math.min(pinned_columns, data[0].length) : 0;
57
- let selected_cells = [];
69
+ const { state: df_state, actions: df_actions } = create_dataframe_context({
70
+ show_fullscreen_button,
71
+ show_copy_button,
72
+ show_search,
73
+ show_row_numbers,
74
+ editable,
75
+ pinned_columns,
76
+ show_label,
77
+ line_breaks,
78
+ wrap,
79
+ max_height,
80
+ column_widths,
81
+ max_chars
82
+ });
58
83
  $:
59
- selected_cells = [...selected_cells];
60
- let selected = false;
84
+ selected_cells = $df_state.ui_state.selected_cells;
85
+ let previous_selected_cells = [];
86
+ $: {
87
+ if (copy_flash && !dequal(selected_cells, previous_selected_cells)) {
88
+ keyboard_ctx?.set_copy_flash(false);
89
+ }
90
+ previous_selected_cells = selected_cells;
91
+ }
61
92
  $:
62
- selected = selected_cells.length > 0 ? selected_cells[selected_cells.length - 1] : false;
93
+ selected = $df_state.ui_state.selected;
63
94
  export let display_value = null;
64
95
  export let styling = null;
65
- let t_rect;
66
96
  let els = {};
67
97
  let data_binding = {};
68
98
  const dispatch = createEventDispatcher();
69
- let editing = false;
99
+ $:
100
+ editing = $df_state.ui_state.editing;
70
101
  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;
102
+ $:
103
+ header_edit = $df_state.ui_state.header_edit;
104
+ $:
105
+ selected_header = $df_state.ui_state.selected_header;
106
+ $:
107
+ active_cell_menu = $df_state.ui_state.active_cell_menu;
108
+ $:
109
+ active_header_menu = $df_state.ui_state.active_header_menu;
75
110
  let is_fullscreen = false;
76
111
  let dragging = false;
77
112
  let copy_flash = false;
@@ -88,278 +123,129 @@ const get_data_at = (row, col) => data?.[row]?.[col]?.value;
88
123
  function make_id() {
89
124
  return Math.random().toString(36).substring(2, 15);
90
125
  }
91
- function make_headers(_head, col_count2, els2) {
92
- let _h = _head || [];
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}`);
95
- _h = _h.concat(fill);
96
- }
97
- if (!_h || _h.length === 0) {
98
- return Array(col_count2[0]).fill(0).map((_, i) => {
99
- const _id = make_id();
100
- els2[_id] = { cell: null, input: null };
101
- return { id: _id, value: JSON.stringify(i + 1) };
102
- });
103
- }
104
- return _h.map((h, i) => {
105
- const _id = make_id();
106
- els2[_id] = { cell: null, input: null };
107
- return { id: _id, value: h ?? "" };
108
- });
109
- }
110
- function process_data(_values) {
111
- const data_row_length = _values.length;
112
- if (data_row_length === 0)
113
- return [];
114
- return Array(row_count[1] === "fixed" ? row_count[0] : data_row_length).fill(0).map((_, i) => {
115
- return Array(
116
- col_count[1] === "fixed" ? col_count[0] : _values[0].length || headers.length
117
- ).fill(0).map((_2, j) => {
118
- const id = make_id();
119
- els[id] = els[id] || { input: null, cell: null };
120
- const obj = { value: _values?.[i]?.[j] ?? "", id };
121
- data_binding[id] = obj;
122
- return obj;
123
- });
124
- });
125
- }
126
- let _headers = make_headers(headers, col_count, els);
126
+ let _headers = make_headers(headers, col_count, els, make_id);
127
127
  let old_headers = headers;
128
128
  $: {
129
129
  if (!dequal(headers, old_headers)) {
130
- _headers = make_headers(headers, col_count, els);
130
+ _headers = make_headers(headers, col_count, els, make_id);
131
131
  old_headers = JSON.parse(JSON.stringify(headers));
132
132
  }
133
133
  }
134
134
  let data = [[]];
135
135
  let old_val = void 0;
136
+ let search_results = [[]];
136
137
  $:
137
138
  if (!dequal(values, old_val)) {
138
- data = process_data(values);
139
- old_val = JSON.parse(JSON.stringify(values));
140
- }
141
- let previous_headers = _headers.map((h) => h.value);
142
- let previous_data = data.map((row) => row.map((cell) => String(cell.value)));
143
- async function trigger_change() {
144
- if (current_search_query)
145
- return;
146
- const current_headers = _headers.map((h) => h.value);
147
- const current_data = data.map(
148
- (row) => row.map((cell) => String(cell.value))
149
- );
150
- if (!dequal(current_data, previous_data) || !dequal(current_headers, previous_headers)) {
151
- dispatch("change", {
152
- data: data.map((row) => row.map((cell) => cell.value)),
153
- headers: _headers.map((h) => h.value),
154
- metadata: null
155
- // the metadata (display value, styling) cannot be changed by the user so we don't need to pass it up
156
- });
157
- if (!value_is_output) {
158
- dispatch("input");
159
- }
160
- previous_data = current_data;
161
- previous_headers = current_headers;
162
- }
163
- }
164
- function get_sort_status(name, _sort, direction) {
165
- if (!_sort)
166
- return "none";
167
- if (headers[_sort] === name) {
168
- if (direction === "asc")
169
- return "ascending";
170
- if (direction === "des")
171
- return "descending";
172
- }
173
- return "none";
174
- }
175
- async function handle_keydown(event) {
176
- if (selected_header !== false && header_edit === false) {
177
- switch (event.key) {
178
- case "ArrowDown":
179
- selected = [0, selected_header];
180
- selected_cells = [[0, selected_header]];
181
- selected_header = false;
182
- return;
183
- case "ArrowLeft":
184
- selected_header = selected_header > 0 ? selected_header - 1 : selected_header;
185
- return;
186
- case "ArrowRight":
187
- selected_header = selected_header < _headers.length - 1 ? selected_header + 1 : selected_header;
188
- return;
189
- case "Escape":
190
- event.preventDefault();
191
- selected_header = false;
192
- break;
193
- case "Enter":
194
- event.preventDefault();
195
- if (editable) {
196
- header_edit = selected_header;
197
- }
198
- break;
199
- }
200
- }
201
- if (event.key === "Delete" || event.key === "Backspace") {
202
- if (!editable)
203
- return;
204
- if (editing) {
205
- const [row, col] = editing;
206
- const input_el = els[data[row][col].id].input;
207
- if (input_el && input_el.selectionStart !== input_el.selectionEnd) {
208
- return;
209
- }
210
- if (event.key === "Delete" && input_el?.selectionStart !== input_el?.value.length) {
211
- return;
212
- }
213
- if (event.key === "Backspace" && input_el?.selectionStart !== 0) {
214
- return;
139
+ if (parent) {
140
+ for (let i = 0; i < 50; i++) {
141
+ parent.style.removeProperty(`--cell-width-${i}`);
215
142
  }
216
143
  }
217
- event.preventDefault();
218
- if (selected_cells.length > 0) {
219
- data = handle_delete_key(data, selected_cells);
220
- dispatch("change", {
221
- data: data.map((row) => row.map((cell) => cell.value)),
222
- headers: _headers.map((h) => h.value),
223
- metadata: null
224
- });
225
- if (!value_is_output) {
226
- dispatch("input");
227
- }
144
+ const is_reset = values.length === 0 || values.length === 1 && values[0].length === 0;
145
+ const is_different_structure = old_val !== void 0 && (values.length !== old_val.length || values[0] && old_val[0] && values[0].length !== old_val[0].length);
146
+ data = process_data(
147
+ values,
148
+ els,
149
+ data_binding,
150
+ make_id,
151
+ display_value
152
+ );
153
+ old_val = JSON.parse(JSON.stringify(values));
154
+ if (is_reset || is_different_structure) {
155
+ df_actions.reset_sort_state();
156
+ } else if ($df_state.sort_state.sort_columns.length > 0) {
157
+ sort_data(data, display_value, styling);
228
158
  }
229
- return;
230
- }
231
- if (event.key === "c" && (event.metaKey || event.ctrlKey)) {
232
- event.preventDefault();
233
- if (selected_cells.length > 0) {
234
- await handle_copy();
159
+ if ($df_state.current_search_query) {
160
+ df_actions.handle_search(null);
161
+ }
162
+ if (parent && cells.length > 0) {
163
+ set_cell_widths();
235
164
  }
236
- return;
237
165
  }
238
- if (!selected) {
239
- return;
166
+ $:
167
+ if ($df_state.current_search_query !== void 0) {
168
+ const cell_map = /* @__PURE__ */ new Map();
169
+ data.forEach((row, row_idx) => {
170
+ row.forEach((cell, col_idx) => {
171
+ cell_map.set(cell.id, {
172
+ value: cell.value,
173
+ styling: styling?.[row_idx]?.[col_idx] || ""
174
+ });
175
+ });
176
+ });
177
+ const filtered = df_actions.filter_data(data);
178
+ search_results = filtered.map(
179
+ (row) => row.map((cell) => {
180
+ const original = cell_map.get(cell.id);
181
+ return {
182
+ ...cell,
183
+ display_value: original?.display_value || String(cell.value),
184
+ styling: original?.styling || ""
185
+ };
186
+ })
187
+ );
240
188
  }
241
- const [i, j] = selected;
242
- switch (event.key) {
243
- case "ArrowRight":
244
- case "ArrowLeft":
245
- case "ArrowDown":
246
- case "ArrowUp":
247
- if (editing)
248
- break;
249
- event.preventDefault();
250
- const next_coords = move_cursor(event.key, [i, j], data);
251
- if (next_coords) {
252
- if (event.shiftKey) {
253
- selected_cells = get_range_selection(
254
- selected_cells.length > 0 ? selected_cells[0] : [i, j],
255
- next_coords
256
- );
257
- editing = false;
258
- } else {
259
- selected_cells = [next_coords];
260
- editing = false;
261
- }
262
- selected = next_coords;
263
- } else if (next_coords === false && event.key === "ArrowUp" && i === 0) {
264
- selected_header = j;
265
- selected = false;
266
- selected_cells = [];
267
- editing = false;
268
- }
269
- break;
270
- case "Escape":
271
- if (!editable)
272
- break;
273
- event.preventDefault();
274
- editing = false;
275
- break;
276
- case "Enter":
277
- event.preventDefault();
278
- if (editable) {
279
- if (event.shiftKey) {
280
- add_row(i);
281
- await tick();
282
- selected = [i + 1, j];
283
- } else {
284
- if (dequal(editing, [i, j])) {
285
- const cell_id = data[i][j].id;
286
- const input_el = els[cell_id].input;
287
- if (input_el) {
288
- data[i][j].value = input_el.value;
289
- }
290
- editing = false;
291
- await tick();
292
- selected = [i, j];
293
- } else {
294
- editing = [i, j];
295
- clear_on_focus = false;
296
- }
297
- }
298
- }
299
- break;
300
- case "Tab":
301
- event.preventDefault();
302
- editing = false;
303
- const next_cell = get_next_cell_coordinates(
304
- [i, j],
305
- data,
306
- event.shiftKey
307
- );
308
- if (next_cell) {
309
- selected_cells = [next_cell];
310
- selected = next_cell;
311
- if (editable) {
312
- editing = next_cell;
313
- clear_on_focus = false;
314
- }
315
- }
316
- break;
317
- default:
318
- if (!editable)
319
- break;
320
- if ((!editing || editing && dequal(editing, [i, j])) && event.key.length === 1) {
321
- clear_on_focus = true;
322
- editing = [i, j];
323
- }
189
+ let previous_headers = _headers.map((h) => h.value);
190
+ let previous_data = data.map((row) => row.map((cell) => String(cell.value)));
191
+ $: {
192
+ if (data || _headers) {
193
+ df_actions.trigger_change(
194
+ data,
195
+ _headers,
196
+ previous_data,
197
+ previous_headers,
198
+ value_is_output,
199
+ dispatch
200
+ );
201
+ previous_data = data.map((row) => row.map((cell) => String(cell.value)));
202
+ previous_headers = _headers.map((h) => h.value);
324
203
  }
325
204
  }
326
- let sort_direction;
327
- let sort_by;
328
205
  function handle_sort(col, direction) {
329
- if (typeof sort_by !== "number" || sort_by !== col) {
330
- sort_direction = direction;
331
- sort_by = col;
332
- } else if (sort_by === col) {
333
- if (sort_direction === direction) {
334
- sort_direction = void 0;
335
- sort_by = void 0;
336
- } else {
337
- sort_direction = direction;
206
+ df_actions.handle_sort(col, direction);
207
+ sort_data(data, display_value, styling);
208
+ }
209
+ function clear_sort() {
210
+ df_actions.reset_sort_state();
211
+ sort_data(data, display_value, styling);
212
+ }
213
+ $: {
214
+ df_actions.sort_data(data, display_value, styling);
215
+ df_actions.update_row_order(data);
216
+ }
217
+ $: {
218
+ if ($df_state.sort_state.sort_columns) {
219
+ if ($df_state.sort_state.sort_columns.length > 0) {
220
+ sort_data(data, display_value, styling);
338
221
  }
339
222
  }
340
223
  }
341
224
  async function edit_header(i, _select = false) {
342
225
  if (!editable || header_edit === i)
343
226
  return;
344
- selected = false;
345
- selected_cells = [];
346
- selected_header = i;
347
- header_edit = i;
227
+ if (!editable || col_count[1] !== "dynamic" || header_edit === i)
228
+ return;
229
+ df_actions.set_header_edit(i);
230
+ }
231
+ function handle_header_click(event, col) {
232
+ if (event.target instanceof HTMLAnchorElement) {
233
+ return;
234
+ }
235
+ event.preventDefault();
236
+ event.stopPropagation();
237
+ if (!editable)
238
+ return;
239
+ clear_on_focus = false;
240
+ df_actions.set_editing(false);
241
+ df_actions.handle_header_click(col, editable);
242
+ parent.focus();
348
243
  }
349
244
  function end_header_edit(event) {
350
245
  if (!editable)
351
246
  return;
352
- switch (event.detail.key) {
353
- case "Escape":
354
- case "Enter":
355
- case "Tab":
356
- event.preventDefault();
357
- selected = false;
358
- selected_header = header_edit;
359
- header_edit = false;
360
- parent.focus();
361
- break;
362
- }
247
+ df_actions.end_header_edit(event.detail.key);
248
+ parent.focus();
363
249
  }
364
250
  async function add_row(index) {
365
251
  parent.focus();
@@ -380,36 +266,32 @@ async function add_row(index) {
380
266
  data = data;
381
267
  selected = [index !== void 0 ? index : data.length - 1, 0];
382
268
  }
383
- $:
384
- (data || _headers) && trigger_change();
385
269
  async function add_col(index) {
386
270
  parent.focus();
387
271
  if (col_count[1] !== "dynamic")
388
272
  return;
389
- const insert_index = index !== void 0 ? index : data[0].length;
390
- for (let i = 0; i < data.length; i++) {
391
- const _id = make_id();
392
- els[_id] = { cell: null, input: null };
393
- data[i].splice(insert_index, 0, { id: _id, value: "" });
394
- }
395
- headers.splice(insert_index, 0, `Header ${headers.length + 1}`);
396
- data = data;
397
- headers = headers;
273
+ const result = df_actions.add_col(data, headers, make_id, index);
274
+ result.data.forEach((row) => {
275
+ row.forEach((cell) => {
276
+ if (!els[cell.id]) {
277
+ els[cell.id] = { cell: null, input: null };
278
+ }
279
+ });
280
+ });
281
+ data = result.data;
282
+ headers = result.headers;
398
283
  await tick();
399
284
  requestAnimationFrame(() => {
400
- edit_header(insert_index, true);
285
+ edit_header(index !== void 0 ? index : data[0].length - 1, true);
401
286
  const new_w = parent.querySelectorAll("tbody")[1].offsetWidth;
402
287
  parent.querySelectorAll("table")[1].scrollTo({ left: new_w });
403
288
  });
404
289
  }
405
290
  function handle_click_outside(event) {
406
291
  if (handle_click_outside_util(event, parent)) {
407
- editing = false;
408
- selected_cells = [];
292
+ df_actions.clear_ui_state();
409
293
  header_edit = false;
410
294
  selected_header = false;
411
- active_cell_menu = null;
412
- active_header_menu = null;
413
295
  }
414
296
  }
415
297
  $:
@@ -426,30 +308,40 @@ function set_cell_widths() {
426
308
  if (show_row_numbers) {
427
309
  parent.style.setProperty(`--cell-width-row-number`, `${widths[0]}px`);
428
310
  }
429
- const data_cells = show_row_numbers ? widths.slice(1) : widths;
430
- data_cells.forEach((width, i) => {
311
+ for (let i = 0; i < 50; i++) {
431
312
  if (!column_widths[i]) {
432
- parent.style.setProperty(
433
- `--cell-width-${i}`,
434
- `${width - scrollbar_width / data_cells.length}px`
435
- );
313
+ parent.style.removeProperty(`--cell-width-${i}`);
314
+ } else if (column_widths[i].endsWith("%")) {
315
+ const percentage = parseFloat(column_widths[i]);
316
+ const pixel_width = Math.floor(percentage / 100 * parent.clientWidth);
317
+ parent.style.setProperty(`--cell-width-${i}`, `${pixel_width}px`);
318
+ } else {
319
+ parent.style.setProperty(`--cell-width-${i}`, column_widths[i]);
320
+ }
321
+ }
322
+ widths.forEach((width, i) => {
323
+ if (!column_widths[i]) {
324
+ const calculated_width = `${Math.max(width, 45)}px`;
325
+ parent.style.setProperty(`--cell-width-${i}`, calculated_width);
436
326
  }
437
327
  });
438
328
  }
439
329
  function get_cell_width(index) {
440
- return column_widths[index] || `var(--cell-width-${index})`;
330
+ return `var(--cell-width-${index})`;
441
331
  }
442
332
  let table_height = values.slice(0, max_height / values.length * 37).length * 37 + 37;
443
333
  let scrollbar_width = 0;
444
- function sort_data(_data, _display_value, _styling, col, dir) {
334
+ function sort_data(_data, _display_value, _styling) {
445
335
  let id = null;
446
336
  if (selected && selected[0] in _data && selected[1] in _data[selected[0]]) {
447
337
  id = _data[selected[0]][selected[1]].id;
448
338
  }
449
- if (typeof col !== "number" || !dir) {
450
- return;
451
- }
452
- sort_table_data(_data, _display_value, _styling, col, dir);
339
+ sort_table_data(
340
+ _data,
341
+ _display_value,
342
+ _styling,
343
+ $df_state.sort_state.sort_columns
344
+ );
453
345
  data = data;
454
346
  if (id) {
455
347
  const [i, j] = get_current_indices(id, data);
@@ -457,7 +349,7 @@ function sort_data(_data, _display_value, _styling, col, dir) {
457
349
  }
458
350
  }
459
351
  $:
460
- sort_data(data, display_value, styling, sort_by, sort_direction);
352
+ sort_data(data, display_value, styling);
461
353
  $:
462
354
  selected_index = !!selected && selected[0];
463
355
  let is_visible = false;
@@ -485,80 +377,58 @@ onMount(() => {
485
377
  );
486
378
  };
487
379
  });
488
- function handle_cell_click(event, row, col) {
489
- if (event.target instanceof HTMLAnchorElement) {
490
- return;
491
- }
492
- event.preventDefault();
493
- event.stopPropagation();
494
- if (show_row_numbers && col === -1)
495
- return;
496
- clear_on_focus = false;
497
- active_cell_menu = null;
498
- active_header_menu = null;
499
- selected_header = false;
500
- header_edit = false;
501
- selected_cells = handle_selection([row, col], selected_cells, event);
502
- parent.focus();
503
- if (editable) {
504
- if (selected_cells.length === 1) {
505
- editing = [row, col];
506
- tick().then(() => {
507
- const input_el = els[data[row][col].id].input;
508
- if (input_el) {
509
- input_el.focus();
510
- input_el.selectionStart = input_el.selectionEnd = input_el.value.length;
511
- }
512
- });
513
- } else {
514
- editing = false;
380
+ $:
381
+ keyboard_ctx = create_keyboard_context({
382
+ selected_header,
383
+ header_edit,
384
+ editing,
385
+ selected,
386
+ selected_cells,
387
+ editable,
388
+ data,
389
+ headers: _headers,
390
+ els,
391
+ df_actions,
392
+ dispatch,
393
+ add_row,
394
+ get_next_cell_coordinates,
395
+ get_range_selection,
396
+ move_cursor,
397
+ copy_flash,
398
+ parent_element: parent,
399
+ set_copy_flash: (value) => {
400
+ copy_flash = value;
401
+ if (value) {
402
+ setTimeout(() => {
403
+ copy_flash = false;
404
+ }, 800);
405
+ }
515
406
  }
516
- }
517
- toggle_cell_button(row, col);
518
- dispatch("select", {
519
- index: [row, col],
520
- value: get_data_at(row, col),
521
- row_value: data[row].map((d) => d.value)
522
407
  });
408
+ $:
409
+ selection_ctx = create_selection_context({
410
+ df_actions,
411
+ dispatch,
412
+ data,
413
+ els,
414
+ editable,
415
+ show_row_numbers,
416
+ get_data_at,
417
+ clear_on_focus,
418
+ selected_cells,
419
+ parent_element: parent
420
+ });
421
+ function handle_cell_click(event, row, col) {
422
+ selection_ctx.actions.handle_cell_click(event, row, col);
523
423
  }
524
424
  function toggle_cell_menu(event, row, col) {
525
- event.stopPropagation();
526
- if (active_cell_menu && active_cell_menu.row === row && active_cell_menu.col === col) {
527
- active_cell_menu = null;
528
- } else {
529
- const cell = event.target.closest("td");
530
- if (cell) {
531
- const rect = cell.getBoundingClientRect();
532
- active_cell_menu = { row, col, x: rect.right, y: rect.bottom };
533
- }
534
- }
425
+ selection_ctx.actions.toggle_cell_menu(event, row, col);
535
426
  }
536
- function add_row_at(index, position) {
537
- const row_index = position === "above" ? index : index + 1;
538
- add_row(row_index);
539
- active_cell_menu = null;
540
- active_header_menu = null;
541
- }
542
- function add_col_at(index, position) {
543
- const col_index = position === "left" ? index : index + 1;
544
- add_col(col_index);
545
- active_cell_menu = null;
546
- active_header_menu = null;
547
- }
548
- function handle_resize() {
549
- active_cell_menu = null;
550
- active_header_menu = null;
551
- selected_cells = [];
552
- selected = false;
553
- editing = false;
554
- set_cell_widths();
555
- }
556
- let active_button = null;
557
- function toggle_header_button(col) {
558
- active_button = active_button?.type === "header" && active_button.col === col ? null : { type: "header", col };
427
+ function handle_select_column(col) {
428
+ selection_ctx.actions.handle_select_column(col);
559
429
  }
560
- function toggle_cell_button(row, col) {
561
- active_button = active_button?.type === "cell" && active_button.row === row && active_button.col === col ? null : { type: "cell", row, col };
430
+ function handle_select_row(row) {
431
+ selection_ctx.actions.handle_select_row(row);
562
432
  }
563
433
  function toggle_fullscreen() {
564
434
  if (!document.fullscreenElement) {
@@ -572,93 +442,44 @@ function toggle_fullscreen() {
572
442
  function handle_fullscreen_change() {
573
443
  is_fullscreen = !!document.fullscreenElement;
574
444
  }
575
- async function handle_copy() {
576
- await copy_table_data(data, selected_cells);
577
- copy_flash = true;
578
- setTimeout(() => {
579
- copy_flash = false;
580
- }, 800);
581
- }
582
445
  function toggle_header_menu(event, col) {
583
446
  event.stopPropagation();
584
447
  if (active_header_menu && active_header_menu.col === col) {
585
- active_header_menu = null;
448
+ df_actions.set_active_header_menu(null);
586
449
  } else {
587
450
  const header = event.target.closest("th");
588
451
  if (header) {
589
452
  const rect = header.getBoundingClientRect();
590
- active_header_menu = { col, x: rect.right, y: rect.bottom };
453
+ df_actions.set_active_header_menu({
454
+ col,
455
+ x: rect.right,
456
+ y: rect.bottom
457
+ });
591
458
  }
592
459
  }
593
460
  }
594
461
  afterUpdate(() => {
595
462
  value_is_output = false;
596
463
  });
597
- async function delete_row(index) {
598
- parent.focus();
599
- if (row_count[1] !== "dynamic")
600
- return;
601
- if (data.length <= 1)
602
- return;
603
- data.splice(index, 1);
604
- data = data;
605
- selected = false;
606
- }
607
- async function delete_col(index) {
608
- parent.focus();
464
+ function delete_col_at(index) {
609
465
  if (col_count[1] !== "dynamic")
610
466
  return;
611
- if (_headers.length <= 1)
467
+ if (data[0].length <= 1)
612
468
  return;
613
- _headers.splice(index, 1);
614
- _headers = _headers;
615
- if (data.length > 0) {
616
- data.forEach((row) => {
617
- row.splice(index, 1);
618
- });
619
- data = data;
620
- }
621
- selected = false;
469
+ const result = df_actions.delete_col_at(data, headers, index);
470
+ data = result.data;
471
+ headers = result.headers;
472
+ _headers = make_headers(headers, col_count, els, make_id);
473
+ df_actions.set_active_cell_menu(null);
474
+ df_actions.set_active_header_menu(null);
475
+ df_actions.set_selected(false);
476
+ df_actions.set_selected_cells([]);
477
+ df_actions.set_editing(false);
622
478
  }
623
479
  function delete_row_at(index) {
624
- delete_row(index);
625
- active_cell_menu = null;
626
- active_header_menu = null;
627
- }
628
- function delete_col_at(index) {
629
- delete_col(index);
630
- active_cell_menu = null;
631
- active_header_menu = null;
632
- }
633
- let row_order = [];
634
- $: {
635
- if (typeof sort_by === "number" && sort_direction && sort_by >= 0 && sort_by < data[0].length) {
636
- const indices = [...Array(data.length)].map((_, i) => i);
637
- const sort_index = sort_by;
638
- indices.sort((a, b) => {
639
- const row_a = data[a];
640
- const row_b = data[b];
641
- if (!row_a || !row_b || sort_index >= row_a.length || sort_index >= row_b.length)
642
- return 0;
643
- const val_a = row_a[sort_index].value;
644
- const val_b = row_b[sort_index].value;
645
- const comp = val_a < val_b ? -1 : val_a > val_b ? 1 : 0;
646
- return sort_direction === "asc" ? comp : -comp;
647
- });
648
- row_order = indices;
649
- } else {
650
- row_order = [...Array(data.length)].map((_, i) => i);
651
- }
652
- }
653
- function handle_select_column(col) {
654
- selected_cells = select_column(data, col);
655
- selected = selected_cells[0];
656
- editing = false;
657
- }
658
- function handle_select_row(row) {
659
- selected_cells = select_row(data, row);
660
- selected = selected_cells[0];
661
- editing = false;
480
+ data = df_actions.delete_row_at(data, index);
481
+ df_actions.set_active_cell_menu(null);
482
+ df_actions.set_active_header_menu(null);
662
483
  }
663
484
  let coords;
664
485
  $:
@@ -677,48 +498,111 @@ $:
677
498
  "--selected-col-pos",
678
499
  positions.col_pos
679
500
  );
680
- if (positions.row_pos) {
681
- document.documentElement.style.setProperty(
682
- "--selected-row-pos",
683
- positions.row_pos
684
- );
685
- }
501
+ document.documentElement.style.setProperty(
502
+ "--selected-row-pos",
503
+ positions.row_pos || "0px"
504
+ );
686
505
  }
687
- let current_search_query = null;
688
- function handle_search(search_query) {
689
- current_search_query = search_query;
690
- dispatch("search", search_query);
691
- }
692
506
  function commit_filter() {
693
- if (current_search_query && show_search === "filter") {
694
- dispatch("change", {
695
- data: data.map((row) => row.map((cell) => cell.value)),
696
- headers: _headers.map((h) => h.value),
697
- metadata: null
507
+ if ($df_state.current_search_query && show_search === "filter") {
508
+ const filtered_data = [];
509
+ const filtered_display_values = [];
510
+ const filtered_styling = [];
511
+ search_results.forEach((row) => {
512
+ const data_row = [];
513
+ const display_row = [];
514
+ const styling_row = [];
515
+ row.forEach((cell) => {
516
+ data_row.push(cell.value);
517
+ display_row.push(cell.display_value || String(cell.value));
518
+ styling_row.push(cell.styling || "");
519
+ });
520
+ filtered_data.push(data_row);
521
+ filtered_display_values.push(display_row);
522
+ filtered_styling.push(styling_row);
698
523
  });
524
+ const change_payload = {
525
+ data: filtered_data,
526
+ headers: _headers.map((h) => h.value),
527
+ metadata: {
528
+ display_value: filtered_display_values,
529
+ styling: filtered_styling
530
+ }
531
+ };
532
+ dispatch("change", change_payload);
699
533
  if (!value_is_output) {
700
534
  dispatch("input");
701
535
  }
702
- current_search_query = null;
536
+ df_actions.handle_search(null);
703
537
  }
704
538
  }
705
- function handle_header_click(event, col) {
706
- if (event.target instanceof HTMLAnchorElement) {
707
- return;
708
- }
709
- event.preventDefault();
710
- event.stopPropagation();
711
- if (!editable)
712
- return;
713
- clear_on_focus = false;
539
+ let viewport;
540
+ let show_scroll_button = false;
541
+ function scroll_to_top() {
542
+ viewport.scrollTo({
543
+ top: 0
544
+ });
545
+ }
546
+ function handle_resize() {
547
+ df_actions.set_active_cell_menu(null);
548
+ df_actions.set_active_header_menu(null);
549
+ selected_cells = [];
550
+ selected = false;
551
+ editing = false;
552
+ set_cell_widths();
553
+ }
554
+ function add_row_at(index, position) {
555
+ const row_index = position === "above" ? index : index + 1;
556
+ add_row(row_index);
714
557
  active_cell_menu = null;
715
558
  active_header_menu = null;
716
- selected = false;
717
- selected_cells = [];
718
- selected_header = col;
719
- header_edit = col;
720
- parent.focus();
721
559
  }
560
+ function add_col_at(index, position) {
561
+ const col_index = position === "left" ? index : index + 1;
562
+ add_col(col_index);
563
+ active_cell_menu = null;
564
+ active_header_menu = null;
565
+ }
566
+ export function reset_sort_state() {
567
+ df_actions.reset_sort_state();
568
+ }
569
+ let is_dragging = false;
570
+ let drag_start = null;
571
+ let mouse_down_pos = null;
572
+ const drag_state = {
573
+ is_dragging,
574
+ drag_start,
575
+ mouse_down_pos
576
+ };
577
+ $: {
578
+ is_dragging = drag_state.is_dragging;
579
+ drag_start = drag_state.drag_start;
580
+ mouse_down_pos = drag_state.mouse_down_pos;
581
+ }
582
+ let drag_handlers;
583
+ function init_drag_handlers() {
584
+ drag_handlers = create_drag_handlers(
585
+ drag_state,
586
+ (value) => is_dragging = value,
587
+ (cells2) => df_actions.set_selected_cells(cells2),
588
+ (cell) => df_actions.set_selected(cell),
589
+ (event, row, col) => selection_ctx.actions.handle_cell_click(event, row, col),
590
+ show_row_numbers,
591
+ parent
592
+ );
593
+ }
594
+ $:
595
+ if (parent)
596
+ init_drag_handlers();
597
+ $:
598
+ handle_mouse_down = drag_handlers?.handle_mouse_down || (() => {
599
+ });
600
+ $:
601
+ handle_mouse_move = drag_handlers?.handle_mouse_move || (() => {
602
+ });
603
+ $:
604
+ handle_mouse_up = drag_handlers?.handle_mouse_up || (() => {
605
+ });
722
606
  </script>
723
607
 
724
608
  <svelte:window on:resize={() => set_cell_widths()} />
@@ -735,140 +619,70 @@ function handle_header_click(event, col) {
735
619
  {show_fullscreen_button}
736
620
  {is_fullscreen}
737
621
  on:click={toggle_fullscreen}
738
- on_copy={handle_copy}
622
+ on_copy={async () => await copy_table_data(data, null)}
739
623
  {show_copy_button}
740
624
  {show_search}
741
- on:search={(e) => handle_search(e.detail)}
625
+ on:search={(e) => df_actions.handle_search(e.detail)}
742
626
  on_commit_filter={commit_filter}
743
- {current_search_query}
627
+ current_search_query={$df_state.current_search_query}
744
628
  />
745
629
  </div>
746
630
  {/if}
747
631
  <div
748
632
  bind:this={parent}
749
633
  class="table-wrap"
750
- class:dragging
634
+ class:dragging={is_dragging}
751
635
  class:no-wrap={!wrap}
752
636
  style="height:{table_height}px;"
753
637
  class:menu-open={active_cell_menu || active_header_menu}
754
- on:keydown={(e) => handle_keydown(e)}
638
+ on:keydown={(e) => handle_keydown(e, keyboard_ctx)}
639
+ on:mousemove={handle_mouse_move}
640
+ on:mouseup={handle_mouse_up}
641
+ on:mouseleave={handle_mouse_up}
755
642
  role="grid"
756
643
  tabindex="0"
757
644
  >
758
- {#if selected !== false && selected_cells.length === 1}
759
- <button
760
- class="selection-button selection-button-column"
761
- on:click|stopPropagation={() => handle_select_column(coords[1])}
762
- aria-label="Select column"
763
- >
764
- &#8942;
765
- </button>
766
- <button
767
- class="selection-button selection-button-row"
768
- on:click|stopPropagation={() => handle_select_row(coords[0])}
769
- aria-label="Select row"
770
- >
771
- &#8942;
772
- </button>
773
- {/if}
774
- <table
775
- bind:contentRect={t_rect}
776
- bind:this={table}
777
- class:fixed-layout={column_widths.length != 0}
778
- >
645
+ <table bind:this={table}>
779
646
  {#if label && label.length !== 0}
780
647
  <caption class="sr-only">{label}</caption>
781
648
  {/if}
782
649
  <thead>
783
650
  <tr>
784
651
  {#if show_row_numbers}
785
- <th
786
- class="row-number-header frozen-column always-frozen"
787
- style="left: 0;"
788
- >
789
- <div class="cell-wrap">
790
- <div class="header-content">
791
- <div class="header-text"></div>
792
- </div>
793
- </div>
794
- </th>
652
+ <RowNumber is_header={true} />
795
653
  {/if}
796
654
  {#each _headers as { value, id }, i (id)}
797
- <th
798
- class:frozen-column={i < actual_pinned_columns}
799
- class:last-frozen={i === actual_pinned_columns - 1}
800
- class:focus={header_edit === i || selected_header === i}
801
- aria-sort={get_sort_status(value, sort_by, sort_direction)}
802
- style="width: {get_cell_width(i)}; left: {i <
803
- actual_pinned_columns
804
- ? i === 0
805
- ? show_row_numbers
806
- ? 'var(--cell-width-row-number)'
807
- : '0'
808
- : `calc(${show_row_numbers ? 'var(--cell-width-row-number) + ' : ''}${Array(
809
- i
810
- )
811
- .fill(0)
812
- .map((_, idx) => `var(--cell-width-${idx})`)
813
- .join(' + ')})`
814
- : 'auto'};"
815
- on:click={(event) => handle_header_click(event, i)}
816
- on:mousedown={(event) => {
817
- event.preventDefault();
818
- event.stopPropagation();
819
- }}
820
- >
821
- <div class="cell-wrap">
822
- <div class="header-content">
823
- <EditableCell
824
- {max_chars}
825
- bind:value={_headers[i].value}
826
- bind:el={els[id].input}
827
- {latex_delimiters}
828
- {line_breaks}
829
- edit={header_edit === i}
830
- on:keydown={end_header_edit}
831
- header
832
- {root}
833
- {editable}
834
- />
835
- {#if header_edit !== i}
836
- <div class="sort-buttons">
837
- <SortIcon
838
- direction={sort_by === i ? sort_direction : null}
839
- on:sort={({ detail }) => handle_sort(i, detail)}
840
- {i18n}
841
- />
842
- </div>
843
- {/if}
844
- </div>
845
- {#if editable}
846
- <button
847
- class="cell-menu-button"
848
- on:click={(event) => toggle_header_menu(event, i)}
849
- on:touchstart={(event) => {
850
- event.preventDefault();
851
- const touch = event.touches[0];
852
- const mouseEvent = new MouseEvent("click", {
853
- clientX: touch.clientX,
854
- clientY: touch.clientY,
855
- bubbles: true,
856
- cancelable: true,
857
- view: window
858
- });
859
- toggle_header_menu(mouseEvent, i);
860
- }}
861
- >
862
- &#8942;
863
- </button>
864
- {/if}
865
- </div>
866
- </th>
655
+ <TableHeader
656
+ bind:value={_headers[i].value}
657
+ {i}
658
+ {actual_pinned_columns}
659
+ {header_edit}
660
+ {selected_header}
661
+ get_sort_status={df_actions.get_sort_status}
662
+ {headers}
663
+ {get_cell_width}
664
+ {handle_header_click}
665
+ {toggle_header_menu}
666
+ {end_header_edit}
667
+ sort_columns={$df_state.sort_state.sort_columns}
668
+ {latex_delimiters}
669
+ {line_breaks}
670
+ {max_chars}
671
+ {root}
672
+ {editable}
673
+ is_static={static_columns.includes(i)}
674
+ {i18n}
675
+ bind:el={els[id].input}
676
+ {col_count}
677
+ />
867
678
  {/each}
868
679
  </tr>
869
680
  </thead>
870
681
  <tbody>
871
682
  <tr>
683
+ {#if show_row_numbers}
684
+ <RowNumber index={0} />
685
+ {/if}
872
686
  {#each max as { value, id }, j (id)}
873
687
  <td tabindex="-1" bind:this={cells[j]}>
874
688
  <div class="cell-wrap">
@@ -881,6 +695,14 @@ function handle_header_click(event, col) {
881
695
  el={null}
882
696
  {root}
883
697
  {editable}
698
+ {i18n}
699
+ show_selection_buttons={selected_cells.length === 1 &&
700
+ selected_cells[0][0] === 0 &&
701
+ selected_cells[0][1] === j}
702
+ coords={[0, j]}
703
+ on_select_column={handle_select_column}
704
+ on_select_row={handle_select_row}
705
+ {is_dragging}
884
706
  />
885
707
  </div>
886
708
  </td>
@@ -903,7 +725,8 @@ function handle_header_click(event, col) {
903
725
  _headers = make_headers(
904
726
  head.map((h) => h ?? ""),
905
727
  col_count,
906
- els
728
+ els,
729
+ make_id
907
730
  );
908
731
  return _headers;
909
732
  },
@@ -916,258 +739,159 @@ function handle_header_click(event, col) {
916
739
  >
917
740
  <div class="table-wrap">
918
741
  <VirtualTable
919
- bind:items={data}
742
+ bind:items={search_results}
920
743
  {max_height}
921
744
  bind:actual_height={table_height}
922
745
  bind:table_scrollbar_width={scrollbar_width}
923
746
  selected={selected_index}
924
747
  disable_scroll={active_cell_menu !== null ||
925
748
  active_header_menu !== null}
749
+ bind:viewport
750
+ bind:show_scroll_button
751
+ on:scroll_top={(_) => {}}
926
752
  >
927
753
  {#if label && label.length !== 0}
928
754
  <caption class="sr-only">{label}</caption>
929
755
  {/if}
930
756
  <tr slot="thead">
931
757
  {#if show_row_numbers}
932
- <th
933
- class="row-number-header frozen-column always-frozen"
934
- style="left: 0;"
935
- >
936
- <div class="cell-wrap">
937
- <div class="header-content">
938
- <div class="header-text"></div>
939
- </div>
940
- </div>
941
- </th>
758
+ <RowNumber is_header={true} />
942
759
  {/if}
943
760
  {#each _headers as { value, id }, i (id)}
944
- <th
945
- class:frozen-column={i < actual_pinned_columns}
946
- class:last-frozen={i === actual_pinned_columns - 1}
947
- class:focus={header_edit === i || selected_header === i}
948
- aria-sort={get_sort_status(value, sort_by, sort_direction)}
949
- style="width: {get_cell_width(i)}; left: {i <
950
- actual_pinned_columns
951
- ? i === 0
952
- ? show_row_numbers
953
- ? 'var(--cell-width-row-number)'
954
- : '0'
955
- : `calc(${show_row_numbers ? 'var(--cell-width-row-number) + ' : ''}${Array(
956
- i
957
- )
958
- .fill(0)
959
- .map((_, idx) => `var(--cell-width-${idx})`)
960
- .join(' + ')})`
961
- : 'auto'};"
962
- on:click={(event) => handle_header_click(event, i)}
963
- on:mousedown={(event) => {
964
- event.preventDefault();
965
- event.stopPropagation();
966
- }}
967
- >
968
- <div class="cell-wrap">
969
- <div class="header-content">
970
- <EditableCell
971
- {max_chars}
972
- bind:value={_headers[i].value}
973
- bind:el={els[id].input}
974
- {latex_delimiters}
975
- {line_breaks}
976
- edit={header_edit === i}
977
- header
978
- {root}
979
- {editable}
980
- />
981
- {#if header_edit !== i}
982
- <div class="sort-buttons">
983
- <SortIcon
984
- direction={sort_by === i ? sort_direction : null}
985
- on:sort={({ detail }) => handle_sort(i, detail)}
986
- {i18n}
987
- />
988
- </div>
989
- {/if}
990
- </div>
991
- {#if editable}
992
- <button
993
- class="cell-menu-button"
994
- on:click={(event) => toggle_header_menu(event, i)}
995
- on:touchstart={(event) => {
996
- event.preventDefault();
997
- const touch = event.touches[0];
998
- const mouseEvent = new MouseEvent("click", {
999
- clientX: touch.clientX,
1000
- clientY: touch.clientY,
1001
- bubbles: true,
1002
- cancelable: true,
1003
- view: window
1004
- });
1005
- toggle_header_menu(mouseEvent, i);
1006
- }}
1007
- >
1008
- &#8942;
1009
- </button>
1010
- {/if}
1011
- </div>
1012
- </th>
761
+ <TableHeader
762
+ bind:value={_headers[i].value}
763
+ {i}
764
+ {actual_pinned_columns}
765
+ {header_edit}
766
+ {selected_header}
767
+ get_sort_status={df_actions.get_sort_status}
768
+ {headers}
769
+ {get_cell_width}
770
+ {handle_header_click}
771
+ {toggle_header_menu}
772
+ {end_header_edit}
773
+ sort_columns={$df_state.sort_state.sort_columns}
774
+ {latex_delimiters}
775
+ {line_breaks}
776
+ {max_chars}
777
+ {root}
778
+ {editable}
779
+ is_static={static_columns.includes(i)}
780
+ {i18n}
781
+ bind:el={els[id].input}
782
+ {col_count}
783
+ />
1013
784
  {/each}
1014
785
  </tr>
1015
- <tr slot="tbody" let:item let:index class:row_odd={index % 2 === 0}>
786
+ <tr slot="tbody" let:item let:index class:row-odd={index % 2 === 0}>
1016
787
  {#if show_row_numbers}
1017
- <td
1018
- class="row-number frozen-column always-frozen"
1019
- style="left: 0;"
1020
- tabindex="-1"
1021
- >
1022
- {index + 1}
1023
- </td>
788
+ <RowNumber {index} />
1024
789
  {/if}
1025
790
  {#each item as { value, id }, j (id)}
1026
- <td
1027
- class:frozen-column={j < actual_pinned_columns}
1028
- class:last-frozen={j === actual_pinned_columns - 1}
1029
- tabindex={show_row_numbers && j === 0 ? -1 : 0}
1030
- bind:this={els[id].cell}
1031
- on:touchstart={(event) => {
1032
- const touch = event.touches[0];
1033
- const mouseEvent = new MouseEvent("click", {
1034
- clientX: touch.clientX,
1035
- clientY: touch.clientY,
1036
- bubbles: true,
1037
- cancelable: true,
1038
- view: window
1039
- });
1040
- handle_cell_click(mouseEvent, index, j);
1041
- }}
1042
- on:mousedown={(event) => {
1043
- event.preventDefault();
1044
- event.stopPropagation();
1045
- }}
1046
- on:click={(event) => handle_cell_click(event, index, j)}
1047
- style="width: {get_cell_width(j)}; left: {j <
1048
- actual_pinned_columns
1049
- ? j === 0
1050
- ? show_row_numbers
1051
- ? 'var(--cell-width-row-number)'
1052
- : '0'
1053
- : `calc(${show_row_numbers ? 'var(--cell-width-row-number) + ' : ''}${Array(
1054
- j
1055
- )
1056
- .fill(0)
1057
- .map((_, idx) => `var(--cell-width-${idx})`)
1058
- .join(' + ')})`
1059
- : 'auto'}; {styling?.[index]?.[j] || ''}"
1060
- class:flash={copy_flash &&
1061
- is_cell_selected([index, j], selected_cells)}
1062
- class={is_cell_selected([index, j], selected_cells)}
1063
- class:menu-active={active_cell_menu &&
1064
- active_cell_menu.row === index &&
1065
- active_cell_menu.col === j}
1066
- >
1067
- <div class="cell-wrap">
1068
- <EditableCell
1069
- bind:value={data[index][j].value}
1070
- bind:el={els[id].input}
1071
- display_value={display_value?.[index]?.[j]}
1072
- {latex_delimiters}
1073
- {line_breaks}
1074
- {editable}
1075
- edit={dequal(editing, [index, j])}
1076
- datatype={Array.isArray(datatype) ? datatype[j] : datatype}
1077
- on:blur={() => {
1078
- clear_on_focus = false;
1079
- parent.focus();
1080
- }}
1081
- on:focus={() => {
1082
- const row = index;
1083
- const col = j;
1084
- if (
1085
- !selected_cells.some(([r, c]) => r === row && c === col)
1086
- ) {
1087
- selected_cells = [[row, col]];
1088
- }
1089
- }}
1090
- {clear_on_focus}
1091
- {root}
1092
- {max_chars}
1093
- />
1094
- {#if editable && should_show_cell_menu([index, j], selected_cells, editable)}
1095
- <button
1096
- class="cell-menu-button"
1097
- on:click={(event) => toggle_cell_menu(event, index, j)}
1098
- >
1099
- &#8942;
1100
- </button>
1101
- {/if}
1102
- </div>
1103
- </td>
791
+ <TableCell
792
+ bind:value={search_results[index][j].value}
793
+ {index}
794
+ {j}
795
+ {actual_pinned_columns}
796
+ {get_cell_width}
797
+ handle_cell_click={(e, r, c) => handle_mouse_down(e, r, c)}
798
+ {toggle_cell_menu}
799
+ {is_cell_selected}
800
+ {should_show_cell_menu}
801
+ {selected_cells}
802
+ {copy_flash}
803
+ {active_cell_menu}
804
+ styling={search_results[index][j].styling}
805
+ {latex_delimiters}
806
+ {line_breaks}
807
+ datatype={Array.isArray(datatype) ? datatype[j] : datatype}
808
+ {editing}
809
+ {clear_on_focus}
810
+ {max_chars}
811
+ {root}
812
+ {editable}
813
+ is_static={static_columns.includes(j)}
814
+ {i18n}
815
+ {components}
816
+ {handle_select_column}
817
+ {handle_select_row}
818
+ bind:el={els[id]}
819
+ {is_dragging}
820
+ />
1104
821
  {/each}
1105
822
  </tr>
1106
823
  </VirtualTable>
1107
824
  </div>
1108
825
  </Upload>
826
+ {#if show_scroll_button}
827
+ <button class="scroll-top-button" on:click={scroll_to_top}>
828
+ &uarr;
829
+ </button>
830
+ {/if}
1109
831
  </div>
1110
832
  </div>
1111
833
  {#if data.length === 0 && editable && row_count[1] === "dynamic"}
1112
- <div class="add-row-container">
1113
- <button class="add-row-button" on:click={() => add_row()}>
1114
- <span>+</span>
1115
- </button>
1116
- </div>
834
+ <EmptyRowButton on_click={() => add_row()} />
1117
835
  {/if}
1118
836
 
1119
- {#if active_cell_menu}
837
+ {#if active_cell_menu || active_header_menu}
1120
838
  <CellMenu
1121
- x={active_cell_menu.x}
1122
- y={active_cell_menu.y}
1123
- row={active_cell_menu.row}
1124
- {col_count}
1125
- {row_count}
1126
- on_add_row_above={() => add_row_at(active_cell_menu?.row || 0, "above")}
1127
- on_add_row_below={() => add_row_at(active_cell_menu?.row || 0, "below")}
1128
- on_add_column_left={() => add_col_at(active_cell_menu?.col || 0, "left")}
1129
- on_add_column_right={() => add_col_at(active_cell_menu?.col || 0, "right")}
1130
- on_delete_row={() => delete_row_at(active_cell_menu?.row || 0)}
1131
- on_delete_col={() => delete_col_at(active_cell_menu?.col || 0)}
1132
- can_delete_rows={data.length > 1}
1133
- can_delete_cols={data[0].length > 1}
1134
- {i18n}
1135
- />
1136
- {/if}
1137
-
1138
- {#if active_header_menu !== null}
1139
- <CellMenu
1140
- {i18n}
1141
- x={active_header_menu.x}
1142
- y={active_header_menu.y}
1143
- row={-1}
839
+ x={active_cell_menu?.x ?? active_header_menu?.x ?? 0}
840
+ y={active_cell_menu?.y ?? active_header_menu?.y ?? 0}
841
+ row={active_header_menu ? -1 : active_cell_menu?.row ?? 0}
1144
842
  {col_count}
1145
843
  {row_count}
1146
844
  on_add_row_above={() => add_row_at(active_cell_menu?.row ?? -1, "above")}
1147
845
  on_add_row_below={() => add_row_at(active_cell_menu?.row ?? -1, "below")}
1148
- on_add_column_left={() => add_col_at(active_header_menu?.col ?? -1, "left")}
846
+ on_add_column_left={() =>
847
+ add_col_at(
848
+ active_cell_menu?.col ?? active_header_menu?.col ?? -1,
849
+ "left"
850
+ )}
1149
851
  on_add_column_right={() =>
1150
- add_col_at(active_header_menu?.col ?? -1, "right")}
852
+ add_col_at(
853
+ active_cell_menu?.col ?? active_header_menu?.col ?? -1,
854
+ "right"
855
+ )}
1151
856
  on_delete_row={() => delete_row_at(active_cell_menu?.row ?? -1)}
1152
- on_delete_col={() => delete_col_at(active_header_menu?.col ?? -1)}
1153
- can_delete_rows={false}
1154
- can_delete_cols={_headers.length > 1}
857
+ on_delete_col={() =>
858
+ delete_col_at(active_cell_menu?.col ?? active_header_menu?.col ?? -1)}
859
+ can_delete_rows={!active_header_menu && data.length > 1}
860
+ can_delete_cols={data.length > 0 && data[0]?.length > 1}
861
+ {i18n}
862
+ on_sort={active_header_menu
863
+ ? (direction) => {
864
+ if (active_header_menu) {
865
+ handle_sort(active_header_menu.col, direction);
866
+ df_actions.set_active_header_menu(null);
867
+ }
868
+ }
869
+ : undefined}
870
+ on_clear_sort={active_header_menu
871
+ ? () => {
872
+ clear_sort();
873
+ df_actions.set_active_header_menu(null);
874
+ }
875
+ : undefined}
876
+ sort_direction={active_header_menu
877
+ ? $df_state.sort_state.sort_columns.find(
878
+ (item) => item.col === (active_header_menu?.col ?? -1)
879
+ )?.direction ?? null
880
+ : null}
881
+ sort_priority={active_header_menu
882
+ ? $df_state.sort_state.sort_columns.findIndex(
883
+ (item) => item.col === (active_header_menu?.col ?? -1)
884
+ ) + 1 || null
885
+ : null}
1155
886
  />
1156
887
  {/if}
1157
888
 
1158
889
  <style>
1159
- .label p {
1160
- position: relative;
1161
- z-index: var(--layer-4);
1162
- margin-bottom: var(--size-2);
1163
- color: var(--block-label-text-color);
1164
- font-size: var(--block-label-text-size);
1165
- }
1166
-
1167
890
  .table-container {
1168
891
  display: flex;
1169
892
  flex-direction: column;
1170
893
  gap: var(--size-2);
894
+ position: relative;
1171
895
  }
1172
896
 
1173
897
  .table-wrap {
@@ -1183,17 +907,26 @@ function handle_header_click(event, col) {
1183
907
  outline: none;
1184
908
  }
1185
909
 
1186
- .dragging {
1187
- border-color: var(--color-accent);
910
+ .table-wrap.dragging {
911
+ cursor: crosshair !important;
912
+ user-select: none;
1188
913
  }
1189
914
 
1190
- .no-wrap {
1191
- white-space: nowrap;
915
+ .table-wrap.dragging * {
916
+ cursor: crosshair !important;
917
+ user-select: none;
918
+ }
919
+
920
+ .table-wrap > :global(button) {
921
+ border: 1px solid var(--border-color-primary);
922
+ border-radius: var(--table-radius);
923
+ overflow: hidden;
1192
924
  }
1193
925
 
1194
926
  table {
1195
927
  position: absolute;
1196
928
  opacity: 0;
929
+ z-index: -1;
1197
930
  transition: 150ms;
1198
931
  width: var(--size-full);
1199
932
  table-layout: auto;
@@ -1205,159 +938,39 @@ function handle_header_click(event, col) {
1205
938
  border-collapse: separate;
1206
939
  }
1207
940
 
1208
- .table-wrap > :global(button) {
1209
- border: 1px solid var(--border-color-primary);
1210
- border-radius: var(--table-radius);
1211
- overflow: hidden;
1212
- }
1213
-
1214
- div:not(.no-wrap) td {
1215
- overflow-wrap: anywhere;
1216
- }
1217
-
1218
- div.no-wrap td {
1219
- overflow-x: hidden;
1220
- }
1221
-
1222
- table.fixed-layout {
1223
- table-layout: fixed;
1224
- }
1225
-
1226
941
  thead {
1227
942
  position: sticky;
1228
943
  top: 0;
1229
- z-index: var(--layer-2);
944
+ z-index: 5;
1230
945
  box-shadow: var(--shadow-drop);
1231
946
  }
1232
947
 
1233
- tr {
1234
- border-bottom: 1px solid var(--border-color-primary);
1235
- text-align: left;
1236
- }
1237
-
1238
- tr > * + * {
1239
- border-right-width: 0px;
1240
- border-left-width: 1px;
1241
- border-style: solid;
1242
- border-color: var(--border-color-primary);
1243
- }
1244
-
1245
- th,
1246
- td {
1247
- --ring-color: transparent;
1248
- position: relative;
1249
- outline: none;
1250
- box-shadow: inset 0 0 0 1px var(--ring-color);
1251
- padding: 0;
1252
- }
1253
-
1254
- th:first-child {
1255
- border-top-left-radius: var(--table-radius);
1256
- border-bottom-left-radius: var(--table-radius);
1257
- }
1258
-
1259
- th:last-child {
1260
- border-top-right-radius: var(--table-radius);
1261
- border-bottom-right-radius: var(--table-radius);
1262
- }
1263
-
1264
- th.focus,
1265
- td.focus {
1266
- --ring-color: var(--color-accent);
1267
- box-shadow: inset 0 0 0 2px var(--ring-color);
1268
- z-index: var(--layer-1);
1269
- }
1270
-
1271
- th.focus {
1272
- z-index: var(--layer-2);
1273
- }
1274
-
1275
- tr:last-child td:first-child {
1276
- border-bottom-left-radius: var(--table-radius);
1277
- }
1278
-
1279
- tr:last-child td:last-child {
1280
- border-bottom-right-radius: var(--table-radius);
1281
- }
1282
-
1283
- tr th {
1284
- background: var(--table-even-background-fill);
948
+ thead :global(th.pinned-column) {
949
+ position: sticky;
950
+ z-index: 6;
951
+ background: var(--table-even-background-fill) !important;
1285
952
  }
1286
953
 
1287
- .sort-buttons {
1288
- display: flex;
1289
- align-items: center;
1290
- flex-shrink: 0;
1291
- order: -1;
954
+ .dragging {
955
+ border-color: var(--color-accent);
1292
956
  }
1293
957
 
1294
- .editing {
1295
- background: var(--table-editing);
958
+ .no-wrap {
959
+ white-space: nowrap;
1296
960
  }
1297
961
 
1298
- .cell-wrap {
1299
- display: flex;
1300
- align-items: center;
1301
- justify-content: flex-start;
1302
- outline: none;
1303
- min-height: var(--size-9);
1304
- position: relative;
1305
- height: 100%;
1306
- padding: var(--size-2);
1307
- box-sizing: border-box;
1308
- margin: 0;
1309
- gap: var(--size-1);
1310
- overflow: visible;
1311
- min-width: 0;
1312
- border-radius: var(--table-radius);
962
+ div:not(.no-wrap) td {
963
+ overflow-wrap: anywhere;
1313
964
  }
1314
965
 
1315
- .header-content {
1316
- display: flex;
1317
- align-items: center;
1318
- overflow: hidden;
1319
- flex-grow: 1;
1320
- min-width: 0;
1321
- white-space: normal;
1322
- overflow-wrap: break-word;
1323
- word-break: normal;
1324
- height: 100%;
1325
- gap: var(--size-1);
966
+ div.no-wrap td {
967
+ overflow-x: hidden;
1326
968
  }
1327
969
 
1328
- .row_odd {
970
+ .row-odd {
1329
971
  background: var(--table-odd-background-fill);
1330
972
  }
1331
973
 
1332
- .row_odd.focus {
1333
- background: var(--background-fill-primary);
1334
- }
1335
-
1336
- .cell-menu-button {
1337
- flex-shrink: 0;
1338
- display: none;
1339
- background-color: var(--block-background-fill);
1340
- border: 1px solid var(--border-color-primary);
1341
- border-radius: var(--block-radius);
1342
- width: var(--size-5);
1343
- height: var(--size-5);
1344
- min-width: var(--size-5);
1345
- padding: 0;
1346
- margin-right: var(--spacing-sm);
1347
- z-index: var(--layer-1);
1348
- position: absolute;
1349
- right: var(--size-1);
1350
- top: 50%;
1351
- transform: translateY(-50%);
1352
- }
1353
-
1354
- .cell-selected .cell-menu-button,
1355
- th:hover .cell-menu-button {
1356
- display: flex;
1357
- align-items: center;
1358
- justify-content: center;
1359
- }
1360
-
1361
974
  .header-row {
1362
975
  display: flex;
1363
976
  justify-content: flex-end;
@@ -1368,225 +981,45 @@ function handle_header_click(event, col) {
1368
981
  width: 100%;
1369
982
  }
1370
983
 
1371
- .label {
984
+ .header-row .label {
1372
985
  flex: 1 1 auto;
1373
986
  margin-right: auto;
1374
987
  }
1375
988
 
1376
- .label p {
989
+ .header-row .label p {
1377
990
  margin: 0;
1378
991
  color: var(--block-label-text-color);
1379
992
  font-size: var(--block-label-text-size);
1380
993
  line-height: var(--line-sm);
1381
- }
1382
-
1383
- .toolbar {
1384
- flex: 0 0 auto;
1385
- }
1386
-
1387
- .row-number,
1388
- .row-number-header {
1389
- text-align: center;
1390
- background: var(--table-even-background-fill);
1391
- font-size: var(--input-text-size);
1392
- color: var(--body-text-color);
1393
- padding: var(--size-1);
1394
- min-width: var(--size-12);
1395
- width: var(--size-12);
1396
- overflow: hidden;
1397
- text-overflow: ellipsis;
1398
- white-space: nowrap;
1399
- font-weight: var(--weight-semibold);
1400
- }
1401
-
1402
- .row-number-header .header-content {
1403
- justify-content: space-between;
1404
- padding: var(--size-1);
1405
- height: var(--size-9);
1406
- display: flex;
1407
- align-items: center;
1408
- }
1409
-
1410
- .row-number-header :global(.sort-icons) {
1411
- margin-right: 0;
1412
- }
1413
-
1414
- :global(tbody > tr:nth-child(odd)) .row-number {
1415
- background: var(--table-odd-background-fill);
1416
- }
1417
-
1418
- .cell-selected {
1419
- --ring-color: var(--color-accent);
1420
- box-shadow: inset 0 0 0 2px var(--ring-color);
1421
- z-index: var(--layer-1);
1422
994
  position: relative;
995
+ z-index: 4;
1423
996
  }
1424
997
 
1425
- .cell-selected.no-top {
1426
- box-shadow:
1427
- inset 2px 0 0 var(--ring-color),
1428
- inset -2px 0 0 var(--ring-color),
1429
- inset 0 -2px 0 var(--ring-color);
1430
- }
1431
-
1432
- .cell-selected.no-bottom {
1433
- box-shadow:
1434
- inset 2px 0 0 var(--ring-color),
1435
- inset -2px 0 0 var(--ring-color),
1436
- inset 0 2px 0 var(--ring-color);
1437
- }
1438
-
1439
- .cell-selected.no-left {
1440
- box-shadow:
1441
- inset 0 2px 0 var(--ring-color),
1442
- inset -2px 0 0 var(--ring-color),
1443
- inset 0 -2px 0 var(--ring-color);
1444
- }
1445
-
1446
- .cell-selected.no-right {
1447
- box-shadow:
1448
- inset 0 2px 0 var(--ring-color),
1449
- inset 2px 0 0 var(--ring-color),
1450
- inset 0 -2px 0 var(--ring-color);
1451
- }
1452
-
1453
- .cell-selected.no-top.no-left {
1454
- box-shadow:
1455
- inset -2px 0 0 var(--ring-color),
1456
- inset 0 -2px 0 var(--ring-color);
1457
- }
1458
-
1459
- .cell-selected.no-top.no-right {
1460
- box-shadow:
1461
- inset 2px 0 0 var(--ring-color),
1462
- inset 0 -2px 0 var(--ring-color);
1463
- }
1464
-
1465
- .cell-selected.no-bottom.no-left {
1466
- box-shadow:
1467
- inset -2px 0 0 var(--ring-color),
1468
- inset 0 2px 0 var(--ring-color);
1469
- }
1470
-
1471
- .cell-selected.no-bottom.no-right {
1472
- box-shadow:
1473
- inset 2px 0 0 var(--ring-color),
1474
- inset 0 2px 0 var(--ring-color);
1475
- }
1476
-
1477
- .cell-selected.no-top.no-bottom {
1478
- box-shadow:
1479
- inset 2px 0 0 var(--ring-color),
1480
- inset -2px 0 0 var(--ring-color);
1481
- }
1482
-
1483
- .cell-selected.no-left.no-right {
1484
- box-shadow:
1485
- inset 0 2px 0 var(--ring-color),
1486
- inset 0 -2px 0 var(--ring-color);
1487
- }
1488
-
1489
- .cell-selected.no-top.no-left.no-right {
1490
- box-shadow: inset 0 -2px 0 var(--ring-color);
1491
- }
1492
-
1493
- .cell-selected.no-bottom.no-left.no-right {
1494
- box-shadow: inset 0 2px 0 var(--ring-color);
1495
- }
1496
-
1497
- .cell-selected.no-left.no-top.no-bottom {
1498
- box-shadow: inset -2px 0 0 var(--ring-color);
1499
- }
1500
-
1501
- .cell-selected.no-right.no-top.no-bottom {
1502
- box-shadow: inset 2px 0 0 var(--ring-color);
1503
- }
1504
-
1505
- .cell-selected.no-top.no-bottom.no-left.no-right {
1506
- box-shadow: none;
1507
- }
1508
-
1509
- .selection-button {
998
+ .scroll-top-button {
1510
999
  position: absolute;
1000
+ right: var(--size-4);
1001
+ bottom: var(--size-4);
1002
+ width: var(--size-8);
1003
+ height: var(--size-8);
1004
+ border-radius: var(--table-radius);
1005
+ background: var(--color-accent);
1006
+ color: white;
1007
+ border: none;
1008
+ cursor: pointer;
1511
1009
  display: flex;
1512
1010
  align-items: center;
1513
1011
  justify-content: center;
1514
- background: var(--color-accent);
1515
- color: white;
1516
- border-radius: var(--radius-sm);
1517
- z-index: var(--layer-4);
1518
- }
1519
-
1520
- .selection-button-column {
1521
- width: var(--size-3);
1522
- height: var(--size-5);
1523
- top: -10px;
1524
- left: var(--selected-col-pos);
1525
- transform: rotate(90deg);
1526
- }
1527
-
1528
- .selection-button-row {
1529
- width: var(--size-3);
1530
- height: var(--size-5);
1531
- left: -7px;
1532
- top: calc(var(--selected-row-pos) - var(--size-5) / 2);
1012
+ font-size: var(--text-lg);
1013
+ z-index: 9;
1014
+ opacity: 0.5;
1533
1015
  }
1534
1016
 
1535
- .table-wrap:not(:focus-within) .selection-button {
1536
- opacity: 0;
1537
- pointer-events: none;
1538
- }
1539
-
1540
- .flash.cell-selected {
1541
- animation: flash-color 700ms ease-out;
1542
- }
1543
-
1544
- @keyframes flash-color {
1545
- 0%,
1546
- 30% {
1547
- background: var(--color-accent-copied);
1548
- }
1549
-
1550
- 100% {
1551
- background: transparent;
1552
- }
1553
- }
1554
-
1555
- .frozen-column {
1556
- position: sticky;
1557
- z-index: var(--layer-2);
1558
- border-right: 1px solid var(--border-color-primary);
1559
- }
1560
-
1561
- tr:nth-child(odd) .frozen-column {
1562
- background: var(--table-odd-background-fill);
1563
- }
1564
-
1565
- tr:nth-child(even) .frozen-column {
1566
- background: var(--table-even-background-fill);
1567
- }
1568
-
1569
- .always-frozen {
1570
- z-index: var(--layer-3);
1017
+ .scroll-top-button:hover {
1018
+ opacity: 1;
1571
1019
  }
1572
1020
 
1573
- .add-row-container {
1574
- margin-top: var(--size-2);
1575
- }
1576
-
1577
- .add-row-button {
1578
- width: 100%;
1579
- padding: var(--size-1);
1580
- background: transparent;
1581
- border: 1px dashed var(--border-color-primary);
1582
- border-radius: var(--radius-sm);
1583
- color: var(--body-text-color);
1584
- cursor: pointer;
1585
- transition: all 150ms;
1586
- }
1587
-
1588
- .add-row-button:hover {
1589
- background: var(--background-fill-secondary);
1590
- border-style: solid;
1021
+ tr {
1022
+ border-bottom: 1px solid var(--border-color-primary);
1023
+ text-align: left;
1591
1024
  }
1592
1025
  </style>