@gradio/dataframe 0.16.5 → 0.17.1

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