@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,35 +1,38 @@
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 {
@@ -38,6 +41,13 @@
38
41
  handle_file_upload,
39
42
  sort_table_data
40
43
  } from "./utils/table_utils";
44
+ import { make_headers, process_data } from "./utils/data_processing";
45
+ import { handle_keydown } from "./utils/keyboard_utils";
46
+ import {
47
+ create_drag_handlers,
48
+ type DragState,
49
+ type DragHandlers
50
+ } from "./utils/drag_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,153 @@
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)[][]);
179
+ if (parent) {
180
+ for (let i = 0; i < 50; i++) {
181
+ parent.style.removeProperty(`--cell-width-${i}`);
182
+ }
183
+ }
184
+ // only reset sort state when values are changed
185
+ const is_reset =
186
+ values.length === 0 || (values.length === 1 && values[0].length === 0);
187
+ const is_different_structure =
188
+ old_val !== undefined &&
189
+ (values.length !== old_val.length ||
190
+ (values[0] && old_val[0] && values[0].length !== old_val[0].length));
191
+
192
+ data = process_data(
193
+ values as (string | number)[][],
194
+ els,
195
+ data_binding,
196
+ make_id,
197
+ display_value
198
+ );
214
199
  old_val = JSON.parse(JSON.stringify(values)) as (string | number)[][];
215
- }
216
-
217
- let previous_headers = _headers.map((h) => h.value);
218
- let previous_data = data.map((row) => row.map((cell) => String(cell.value)));
219
200
 
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))
226
- );
201
+ if (is_reset || is_different_structure) {
202
+ df_actions.reset_sort_state();
203
+ } else if ($df_state.sort_state.sort_columns.length > 0) {
204
+ sort_data(data, display_value, styling);
205
+ }
227
206
 
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;
207
+ if ($df_state.current_search_query) {
208
+ df_actions.handle_search(null);
244
209
  }
245
- }
246
210
 
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";
211
+ if (parent && cells.length > 0) {
212
+ set_cell_widths();
256
213
  }
257
- return "none";
258
214
  }
259
215
 
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
- }
290
- }
216
+ $: if ($df_state.current_search_query !== undefined) {
217
+ const cell_map = new Map();
291
218
 
292
- if (event.key === "Delete" || event.key === "Backspace") {
293
- if (!editable) return;
219
+ data.forEach((row, row_idx) => {
220
+ row.forEach((cell, col_idx) => {
221
+ cell_map.set(cell.id, {
222
+ value: cell.value,
223
+ styling: styling?.[row_idx]?.[col_idx] || ""
224
+ });
225
+ });
226
+ });
294
227
 
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
- }
228
+ const filtered = df_actions.filter_data(data);
229
+
230
+ search_results = filtered.map((row) =>
231
+ row.map((cell) => {
232
+ const original = cell_map.get(cell.id);
233
+ return {
234
+ ...cell,
235
+ display_value: original?.display_value || String(cell.value),
236
+ styling: original?.styling || ""
237
+ };
238
+ })
239
+ );
240
+ }
311
241
 
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
319
- });
320
- if (!value_is_output) {
321
- dispatch("input");
322
- }
323
- }
324
- return;
325
- }
242
+ let previous_headers = _headers.map((h) => h.value);
243
+ let previous_data = data.map((row) => row.map((cell) => String(cell.value)));
326
244
 
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;
245
+ $: {
246
+ if (data || _headers) {
247
+ df_actions.trigger_change(
248
+ data,
249
+ _headers,
250
+ previous_data,
251
+ previous_headers,
252
+ value_is_output,
253
+ dispatch
254
+ );
255
+ previous_data = data.map((row) => row.map((cell) => String(cell.value)));
256
+ previous_headers = _headers.map((h) => h.value);
333
257
  }
258
+ }
334
259
 
335
- if (!selected) {
336
- return;
337
- }
260
+ function handle_sort(col: number, direction: SortDirection): void {
261
+ df_actions.handle_sort(col, direction);
262
+ sort_data(data, display_value, styling);
263
+ }
338
264
 
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
- }
428
- }
265
+ function clear_sort(): void {
266
+ df_actions.reset_sort_state();
267
+ sort_data(data, display_value, styling);
429
268
  }
430
269
 
431
- type SortDirection = "asc" | "des";
432
- let sort_direction: SortDirection | undefined;
433
- let sort_by: number | undefined;
270
+ $: {
271
+ df_actions.sort_data(data, display_value, styling);
272
+ df_actions.update_row_order(data);
273
+ }
434
274
 
435
- 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;
275
+ $: {
276
+ if ($df_state.sort_state.sort_columns) {
277
+ if ($df_state.sort_state.sort_columns.length > 0) {
278
+ sort_data(data, display_value, styling);
445
279
  }
446
280
  }
447
281
  }
448
282
 
449
283
  async function edit_header(i: number, _select = false): Promise<void> {
450
284
  if (!editable || header_edit === i) return;
451
- selected = false;
452
- selected_cells = [];
453
- selected_header = i;
454
- header_edit = i;
285
+ if (!editable || col_count[1] !== "dynamic" || header_edit === i) return;
286
+ df_actions.set_header_edit(i);
455
287
  }
456
288
 
457
- function end_header_edit(event: CustomEvent<KeyboardEvent>): void {
289
+ function handle_header_click(event: MouseEvent, col: number): void {
290
+ if (event.target instanceof HTMLAnchorElement) {
291
+ return;
292
+ }
293
+ event.preventDefault();
294
+ event.stopPropagation();
458
295
  if (!editable) return;
296
+ clear_on_focus = false;
297
+ df_actions.set_editing(false);
298
+ df_actions.handle_header_click(col, editable);
299
+ parent.focus();
300
+ }
459
301
 
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
- }
302
+ function end_header_edit(event: CustomEvent<KeyboardEvent>): void {
303
+ if (!editable) return;
304
+ df_actions.end_header_edit(event.detail.key);
305
+ parent.focus();
471
306
  }
472
307
 
473
308
  async function add_row(index?: number): Promise<void> {
@@ -495,29 +330,27 @@
495
330
  selected = [index !== undefined ? index : data.length - 1, 0];
496
331
  }
497
332
 
498
- $: (data || _headers) && trigger_change();
499
-
500
333
  async function add_col(index?: number): Promise<void> {
501
334
  parent.focus();
502
335
  if (col_count[1] !== "dynamic") return;
503
336
 
504
- const insert_index = index !== undefined ? index : data[0].length;
337
+ const result = df_actions.add_col(data, headers, make_id, index);
505
338
 
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
- }
511
-
512
- headers.splice(insert_index, 0, `Header ${headers.length + 1}`);
339
+ result.data.forEach((row) => {
340
+ row.forEach((cell) => {
341
+ if (!els[cell.id]) {
342
+ els[cell.id] = { cell: null, input: null };
343
+ }
344
+ });
345
+ });
513
346
 
514
- data = data;
515
- headers = headers;
347
+ data = result.data;
348
+ headers = result.headers;
516
349
 
517
350
  await tick();
518
351
 
519
352
  requestAnimationFrame(() => {
520
- edit_header(insert_index, true);
353
+ edit_header(index !== undefined ? index : data[0].length - 1, true);
521
354
  const new_w = parent.querySelectorAll("tbody")[1].offsetWidth;
522
355
  parent.querySelectorAll("table")[1].scrollTo({ left: new_w });
523
356
  });
@@ -525,12 +358,9 @@
525
358
 
526
359
  function handle_click_outside(event: Event): void {
527
360
  if (handle_click_outside_util(event, parent)) {
528
- editing = false;
529
- selected_cells = [];
361
+ df_actions.clear_ui_state();
530
362
  header_edit = false;
531
363
  selected_header = false;
532
- active_cell_menu = null;
533
- active_header_menu = null;
534
364
  }
535
365
  }
536
366
 
@@ -548,19 +378,29 @@
548
378
  if (show_row_numbers) {
549
379
  parent.style.setProperty(`--cell-width-row-number`, `${widths[0]}px`);
550
380
  }
551
- const data_cells = show_row_numbers ? widths.slice(1) : widths;
552
- data_cells.forEach((width, i) => {
381
+
382
+ for (let i = 0; i < 50; i++) {
383
+ if (!column_widths[i]) {
384
+ parent.style.removeProperty(`--cell-width-${i}`);
385
+ } else if (column_widths[i].endsWith("%")) {
386
+ const percentage = parseFloat(column_widths[i]);
387
+ const pixel_width = Math.floor((percentage / 100) * parent.clientWidth);
388
+ parent.style.setProperty(`--cell-width-${i}`, `${pixel_width}px`);
389
+ } else {
390
+ parent.style.setProperty(`--cell-width-${i}`, column_widths[i]);
391
+ }
392
+ }
393
+
394
+ widths.forEach((width, i) => {
553
395
  if (!column_widths[i]) {
554
- parent.style.setProperty(
555
- `--cell-width-${i}`,
556
- `${width - scrollbar_width / data_cells.length}px`
557
- );
396
+ const calculated_width = `${Math.max(width, 45)}px`;
397
+ parent.style.setProperty(`--cell-width-${i}`, calculated_width);
558
398
  }
559
399
  });
560
400
  }
561
401
 
562
402
  function get_cell_width(index: number): string {
563
- return column_widths[index] || `var(--cell-width-${index})`;
403
+ return `var(--cell-width-${index})`;
564
404
  }
565
405
 
566
406
  let table_height: number =
@@ -570,19 +410,19 @@
570
410
  function sort_data(
571
411
  _data: typeof data,
572
412
  _display_value: string[][] | null,
573
- _styling: string[][] | null,
574
- col?: number,
575
- dir?: SortDirection
413
+ _styling: string[][] | null
576
414
  ): void {
577
415
  let id = null;
578
416
  if (selected && selected[0] in _data && selected[1] in _data[selected[0]]) {
579
417
  id = _data[selected[0]][selected[1]].id;
580
418
  }
581
- if (typeof col !== "number" || !dir) {
582
- return;
583
- }
584
419
 
585
- sort_table_data(_data, _display_value, _styling, col, dir);
420
+ sort_table_data(
421
+ _data,
422
+ _display_value,
423
+ _styling,
424
+ $df_state.sort_state.sort_columns
425
+ );
586
426
  data = data;
587
427
 
588
428
  if (id) {
@@ -591,7 +431,7 @@
591
431
  }
592
432
  }
593
433
 
594
- $: sort_data(data, display_value, styling, sort_by, sort_direction);
434
+ $: sort_data(data, display_value, styling);
595
435
 
596
436
  $: selected_index = !!selected && selected[0];
597
437
 
@@ -624,114 +464,65 @@
624
464
  };
625
465
  });
626
466
 
467
+ $: keyboard_ctx = create_keyboard_context({
468
+ selected_header,
469
+ header_edit,
470
+ editing,
471
+ selected,
472
+ selected_cells,
473
+ editable,
474
+ data,
475
+ headers: _headers,
476
+ els,
477
+ df_actions,
478
+ dispatch,
479
+ add_row,
480
+ get_next_cell_coordinates,
481
+ get_range_selection,
482
+ move_cursor,
483
+ copy_flash,
484
+ parent_element: parent,
485
+ set_copy_flash: (value: boolean) => {
486
+ copy_flash = value;
487
+ if (value) {
488
+ setTimeout(() => {
489
+ copy_flash = false;
490
+ }, 800);
491
+ }
492
+ }
493
+ });
494
+
495
+ $: selection_ctx = create_selection_context({
496
+ df_actions,
497
+ dispatch,
498
+ data,
499
+ els,
500
+ editable,
501
+ show_row_numbers,
502
+ get_data_at,
503
+ clear_on_focus,
504
+ selected_cells,
505
+ parent_element: parent
506
+ });
507
+
627
508
  function handle_cell_click(
628
509
  event: MouseEvent,
629
510
  row: number,
630
511
  col: number
631
512
  ): 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
- });
513
+ selection_ctx.actions.handle_cell_click(event, row, col);
673
514
  }
674
515
 
675
516
  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;
704
- }
705
-
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();
517
+ selection_ctx.actions.toggle_cell_menu(event, row, col);
713
518
  }
714
519
 
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 };
520
+ function handle_select_column(col: number): void {
521
+ selection_ctx.actions.handle_select_column(col);
726
522
  }
727
523
 
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 };
524
+ function handle_select_row(row: number): void {
525
+ selection_ctx.actions.handle_select_row(row);
735
526
  }
736
527
 
737
528
  function toggle_fullscreen(): void {
@@ -748,23 +539,19 @@
748
539
  is_fullscreen = !!document.fullscreenElement;
749
540
  }
750
541
 
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
542
  function toggle_header_menu(event: MouseEvent, col: number): void {
760
543
  event.stopPropagation();
761
544
  if (active_header_menu && active_header_menu.col === col) {
762
- active_header_menu = null;
545
+ df_actions.set_active_header_menu(null);
763
546
  } else {
764
547
  const header = (event.target as HTMLElement).closest("th");
765
548
  if (header) {
766
549
  const rect = header.getBoundingClientRect();
767
- active_header_menu = { col, x: rect.right, y: rect.bottom };
550
+ df_actions.set_active_header_menu({
551
+ col,
552
+ x: rect.right,
553
+ y: rect.bottom
554
+ });
768
555
  }
769
556
  }
770
557
  }
@@ -773,86 +560,25 @@
773
560
  value_is_output = false;
774
561
  });
775
562
 
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();
563
+ function delete_col_at(index: number): void {
787
564
  if (col_count[1] !== "dynamic") return;
788
- if (_headers.length <= 1) return;
789
-
790
- _headers.splice(index, 1);
791
- _headers = _headers;
565
+ if (data[0].length <= 1) return;
792
566
 
793
- if (data.length > 0) {
794
- data.forEach((row) => {
795
- row.splice(index, 1);
796
- });
797
- data = data;
798
- }
799
- selected = false;
567
+ const result = df_actions.delete_col_at(data, headers, index);
568
+ data = result.data;
569
+ headers = result.headers;
570
+ _headers = make_headers(headers, col_count, els, make_id);
571
+ df_actions.set_active_cell_menu(null);
572
+ df_actions.set_active_header_menu(null);
573
+ df_actions.set_selected(false);
574
+ df_actions.set_selected_cells([]);
575
+ df_actions.set_editing(false);
800
576
  }
801
577
 
802
578
  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;
579
+ data = df_actions.delete_row_at(data, index);
580
+ df_actions.set_active_cell_menu(null);
581
+ df_actions.set_active_header_menu(null);
856
582
  }
857
583
 
858
584
  let coords: CellCoordinate;
@@ -870,55 +596,125 @@
870
596
  "--selected-col-pos",
871
597
  positions.col_pos
872
598
  );
873
- if (positions.row_pos) {
874
- document.documentElement.style.setProperty(
875
- "--selected-row-pos",
876
- positions.row_pos
877
- );
878
- }
599
+ document.documentElement.style.setProperty(
600
+ "--selected-row-pos",
601
+ positions.row_pos || "0px"
602
+ );
879
603
  }
880
604
 
881
- let current_search_query: string | null = null;
605
+ function commit_filter(): void {
606
+ if ($df_state.current_search_query && show_search === "filter") {
607
+ const filtered_data: (string | number)[][] = [];
608
+ const filtered_display_values: string[][] = [];
609
+ const filtered_styling: string[][] = [];
610
+
611
+ search_results.forEach((row) => {
612
+ const data_row: (string | number)[] = [];
613
+ const display_row: string[] = [];
614
+ const styling_row: string[] = [];
615
+
616
+ row.forEach((cell) => {
617
+ data_row.push(cell.value);
618
+ display_row.push(cell.display_value || String(cell.value));
619
+ styling_row.push(cell.styling || "");
620
+ });
882
621
 
883
- function handle_search(search_query: string | null): void {
884
- current_search_query = search_query;
885
- dispatch("search", search_query);
886
- }
622
+ filtered_data.push(data_row);
623
+ filtered_display_values.push(display_row);
624
+ filtered_styling.push(styling_row);
625
+ });
887
626
 
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)),
627
+ const change_payload = {
628
+ data: filtered_data,
892
629
  headers: _headers.map((h) => h.value),
893
- metadata: null
894
- });
630
+ metadata: {
631
+ display_value: filtered_display_values,
632
+ styling: filtered_styling
633
+ }
634
+ };
635
+
636
+ dispatch("change", change_payload);
637
+
895
638
  if (!value_is_output) {
896
639
  dispatch("input");
897
640
  }
898
- current_search_query = null;
641
+
642
+ df_actions.handle_search(null);
899
643
  }
900
644
  }
901
645
 
902
- function handle_header_click(event: MouseEvent, col: number): void {
903
- if (event.target instanceof HTMLAnchorElement) {
904
- return;
905
- }
646
+ let viewport: HTMLTableElement;
647
+ let show_scroll_button = false;
906
648
 
907
- event.preventDefault();
908
- event.stopPropagation();
649
+ function scroll_to_top(): void {
650
+ viewport.scrollTo({
651
+ top: 0
652
+ });
653
+ }
909
654
 
910
- if (!editable) return;
655
+ function handle_resize(): void {
656
+ df_actions.set_active_cell_menu(null);
657
+ df_actions.set_active_header_menu(null);
658
+ selected_cells = [];
659
+ selected = false;
660
+ editing = false;
661
+ set_cell_widths();
662
+ }
911
663
 
912
- clear_on_focus = false;
664
+ function add_row_at(index: number, position: "above" | "below"): void {
665
+ const row_index = position === "above" ? index : index + 1;
666
+ add_row(row_index);
913
667
  active_cell_menu = null;
914
668
  active_header_menu = null;
915
- selected = false;
916
- selected_cells = [];
917
- selected_header = col;
918
- header_edit = col;
669
+ }
919
670
 
920
- parent.focus();
671
+ function add_col_at(index: number, position: "left" | "right"): void {
672
+ const col_index = position === "left" ? index : index + 1;
673
+ add_col(col_index);
674
+ active_cell_menu = null;
675
+ active_header_menu = null;
676
+ }
677
+
678
+ export function reset_sort_state(): void {
679
+ df_actions.reset_sort_state();
680
+ }
681
+
682
+ let is_dragging = false;
683
+ let drag_start: [number, number] | null = null;
684
+ let mouse_down_pos: { x: number; y: number } | null = null;
685
+
686
+ const drag_state: DragState = {
687
+ is_dragging,
688
+ drag_start,
689
+ mouse_down_pos
690
+ };
691
+
692
+ $: {
693
+ is_dragging = drag_state.is_dragging;
694
+ drag_start = drag_state.drag_start;
695
+ mouse_down_pos = drag_state.mouse_down_pos;
696
+ }
697
+
698
+ let drag_handlers: DragHandlers;
699
+
700
+ function init_drag_handlers(): void {
701
+ drag_handlers = create_drag_handlers(
702
+ drag_state,
703
+ (value) => (is_dragging = value),
704
+ (cells) => df_actions.set_selected_cells(cells),
705
+ (cell) => df_actions.set_selected(cell),
706
+ (event, row, col) =>
707
+ selection_ctx.actions.handle_cell_click(event, row, col),
708
+ show_row_numbers,
709
+ parent
710
+ );
921
711
  }
712
+
713
+ $: if (parent) init_drag_handlers();
714
+
715
+ $: handle_mouse_down = drag_handlers?.handle_mouse_down || (() => {});
716
+ $: handle_mouse_move = drag_handlers?.handle_mouse_move || (() => {});
717
+ $: handle_mouse_up = drag_handlers?.handle_mouse_up || (() => {});
922
718
  </script>
923
719
 
924
720
  <svelte:window on:resize={() => set_cell_widths()} />
@@ -935,140 +731,70 @@
935
731
  {show_fullscreen_button}
936
732
  {is_fullscreen}
937
733
  on:click={toggle_fullscreen}
938
- on_copy={handle_copy}
734
+ on_copy={async () => await copy_table_data(data, null)}
939
735
  {show_copy_button}
940
736
  {show_search}
941
- on:search={(e) => handle_search(e.detail)}
737
+ on:search={(e) => df_actions.handle_search(e.detail)}
942
738
  on_commit_filter={commit_filter}
943
- {current_search_query}
739
+ current_search_query={$df_state.current_search_query}
944
740
  />
945
741
  </div>
946
742
  {/if}
947
743
  <div
948
744
  bind:this={parent}
949
745
  class="table-wrap"
950
- class:dragging
746
+ class:dragging={is_dragging}
951
747
  class:no-wrap={!wrap}
952
748
  style="height:{table_height}px;"
953
749
  class:menu-open={active_cell_menu || active_header_menu}
954
- on:keydown={(e) => handle_keydown(e)}
750
+ on:keydown={(e) => handle_keydown(e, keyboard_ctx)}
751
+ on:mousemove={handle_mouse_move}
752
+ on:mouseup={handle_mouse_up}
753
+ on:mouseleave={handle_mouse_up}
955
754
  role="grid"
956
755
  tabindex="0"
957
756
  >
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
- >
757
+ <table bind:this={table}>
979
758
  {#if label && label.length !== 0}
980
759
  <caption class="sr-only">{label}</caption>
981
760
  {/if}
982
761
  <thead>
983
762
  <tr>
984
763
  {#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>
764
+ <RowNumber is_header={true} />
995
765
  {/if}
996
766
  {#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>
767
+ <TableHeader
768
+ bind:value={_headers[i].value}
769
+ {i}
770
+ {actual_pinned_columns}
771
+ {header_edit}
772
+ {selected_header}
773
+ get_sort_status={df_actions.get_sort_status}
774
+ {headers}
775
+ {get_cell_width}
776
+ {handle_header_click}
777
+ {toggle_header_menu}
778
+ {end_header_edit}
779
+ sort_columns={$df_state.sort_state.sort_columns}
780
+ {latex_delimiters}
781
+ {line_breaks}
782
+ {max_chars}
783
+ {root}
784
+ {editable}
785
+ is_static={static_columns.includes(i)}
786
+ {i18n}
787
+ bind:el={els[id].input}
788
+ {col_count}
789
+ />
1067
790
  {/each}
1068
791
  </tr>
1069
792
  </thead>
1070
793
  <tbody>
1071
794
  <tr>
795
+ {#if show_row_numbers}
796
+ <RowNumber index={0} />
797
+ {/if}
1072
798
  {#each max as { value, id }, j (id)}
1073
799
  <td tabindex="-1" bind:this={cells[j]}>
1074
800
  <div class="cell-wrap">
@@ -1081,6 +807,14 @@
1081
807
  el={null}
1082
808
  {root}
1083
809
  {editable}
810
+ {i18n}
811
+ show_selection_buttons={selected_cells.length === 1 &&
812
+ selected_cells[0][0] === 0 &&
813
+ selected_cells[0][1] === j}
814
+ coords={[0, j]}
815
+ on_select_column={handle_select_column}
816
+ on_select_row={handle_select_row}
817
+ {is_dragging}
1084
818
  />
1085
819
  </div>
1086
820
  </td>
@@ -1103,7 +837,8 @@
1103
837
  _headers = make_headers(
1104
838
  head.map((h) => h ?? ""),
1105
839
  col_count,
1106
- els
840
+ els,
841
+ make_id
1107
842
  );
1108
843
  return _headers;
1109
844
  },
@@ -1116,258 +851,159 @@
1116
851
  >
1117
852
  <div class="table-wrap">
1118
853
  <VirtualTable
1119
- bind:items={data}
854
+ bind:items={search_results}
1120
855
  {max_height}
1121
856
  bind:actual_height={table_height}
1122
857
  bind:table_scrollbar_width={scrollbar_width}
1123
858
  selected={selected_index}
1124
859
  disable_scroll={active_cell_menu !== null ||
1125
860
  active_header_menu !== null}
861
+ bind:viewport
862
+ bind:show_scroll_button
863
+ on:scroll_top={(_) => {}}
1126
864
  >
1127
865
  {#if label && label.length !== 0}
1128
866
  <caption class="sr-only">{label}</caption>
1129
867
  {/if}
1130
868
  <tr slot="thead">
1131
869
  {#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>
870
+ <RowNumber is_header={true} />
1142
871
  {/if}
1143
872
  {#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>
873
+ <TableHeader
874
+ bind:value={_headers[i].value}
875
+ {i}
876
+ {actual_pinned_columns}
877
+ {header_edit}
878
+ {selected_header}
879
+ get_sort_status={df_actions.get_sort_status}
880
+ {headers}
881
+ {get_cell_width}
882
+ {handle_header_click}
883
+ {toggle_header_menu}
884
+ {end_header_edit}
885
+ sort_columns={$df_state.sort_state.sort_columns}
886
+ {latex_delimiters}
887
+ {line_breaks}
888
+ {max_chars}
889
+ {root}
890
+ {editable}
891
+ is_static={static_columns.includes(i)}
892
+ {i18n}
893
+ bind:el={els[id].input}
894
+ {col_count}
895
+ />
1213
896
  {/each}
1214
897
  </tr>
1215
- <tr slot="tbody" let:item let:index class:row_odd={index % 2 === 0}>
898
+ <tr slot="tbody" let:item let:index class:row-odd={index % 2 === 0}>
1216
899
  {#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>
900
+ <RowNumber {index} />
1224
901
  {/if}
1225
902
  {#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>
903
+ <TableCell
904
+ bind:value={search_results[index][j].value}
905
+ {index}
906
+ {j}
907
+ {actual_pinned_columns}
908
+ {get_cell_width}
909
+ handle_cell_click={(e, r, c) => handle_mouse_down(e, r, c)}
910
+ {toggle_cell_menu}
911
+ {is_cell_selected}
912
+ {should_show_cell_menu}
913
+ {selected_cells}
914
+ {copy_flash}
915
+ {active_cell_menu}
916
+ styling={search_results[index][j].styling}
917
+ {latex_delimiters}
918
+ {line_breaks}
919
+ datatype={Array.isArray(datatype) ? datatype[j] : datatype}
920
+ {editing}
921
+ {clear_on_focus}
922
+ {max_chars}
923
+ {root}
924
+ {editable}
925
+ is_static={static_columns.includes(j)}
926
+ {i18n}
927
+ {components}
928
+ {handle_select_column}
929
+ {handle_select_row}
930
+ bind:el={els[id]}
931
+ {is_dragging}
932
+ />
1304
933
  {/each}
1305
934
  </tr>
1306
935
  </VirtualTable>
1307
936
  </div>
1308
937
  </Upload>
938
+ {#if show_scroll_button}
939
+ <button class="scroll-top-button" on:click={scroll_to_top}>
940
+ &uarr;
941
+ </button>
942
+ {/if}
1309
943
  </div>
1310
944
  </div>
1311
945
  {#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>
1317
- {/if}
1318
-
1319
- {#if active_cell_menu}
1320
- <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
- />
946
+ <EmptyRowButton on_click={() => add_row()} />
1336
947
  {/if}
1337
948
 
1338
- {#if active_header_menu !== null}
949
+ {#if active_cell_menu || active_header_menu}
1339
950
  <CellMenu
1340
- {i18n}
1341
- x={active_header_menu.x}
1342
- y={active_header_menu.y}
1343
- row={-1}
951
+ x={active_cell_menu?.x ?? active_header_menu?.x ?? 0}
952
+ y={active_cell_menu?.y ?? active_header_menu?.y ?? 0}
953
+ row={active_header_menu ? -1 : active_cell_menu?.row ?? 0}
1344
954
  {col_count}
1345
955
  {row_count}
1346
956
  on_add_row_above={() => add_row_at(active_cell_menu?.row ?? -1, "above")}
1347
957
  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")}
958
+ on_add_column_left={() =>
959
+ add_col_at(
960
+ active_cell_menu?.col ?? active_header_menu?.col ?? -1,
961
+ "left"
962
+ )}
1349
963
  on_add_column_right={() =>
1350
- add_col_at(active_header_menu?.col ?? -1, "right")}
964
+ add_col_at(
965
+ active_cell_menu?.col ?? active_header_menu?.col ?? -1,
966
+ "right"
967
+ )}
1351
968
  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}
969
+ on_delete_col={() =>
970
+ delete_col_at(active_cell_menu?.col ?? active_header_menu?.col ?? -1)}
971
+ can_delete_rows={!active_header_menu && data.length > 1}
972
+ can_delete_cols={data.length > 0 && data[0]?.length > 1}
973
+ {i18n}
974
+ on_sort={active_header_menu
975
+ ? (direction) => {
976
+ if (active_header_menu) {
977
+ handle_sort(active_header_menu.col, direction);
978
+ df_actions.set_active_header_menu(null);
979
+ }
980
+ }
981
+ : undefined}
982
+ on_clear_sort={active_header_menu
983
+ ? () => {
984
+ clear_sort();
985
+ df_actions.set_active_header_menu(null);
986
+ }
987
+ : undefined}
988
+ sort_direction={active_header_menu
989
+ ? $df_state.sort_state.sort_columns.find(
990
+ (item) => item.col === (active_header_menu?.col ?? -1)
991
+ )?.direction ?? null
992
+ : null}
993
+ sort_priority={active_header_menu
994
+ ? $df_state.sort_state.sort_columns.findIndex(
995
+ (item) => item.col === (active_header_menu?.col ?? -1)
996
+ ) + 1 || null
997
+ : null}
1355
998
  />
1356
999
  {/if}
1357
1000
 
1358
1001
  <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
1002
  .table-container {
1368
1003
  display: flex;
1369
1004
  flex-direction: column;
1370
1005
  gap: var(--size-2);
1006
+ position: relative;
1371
1007
  }
1372
1008
 
1373
1009
  .table-wrap {
@@ -1383,17 +1019,26 @@
1383
1019
  outline: none;
1384
1020
  }
1385
1021
 
1386
- .dragging {
1387
- border-color: var(--color-accent);
1022
+ .table-wrap.dragging {
1023
+ cursor: crosshair !important;
1024
+ user-select: none;
1388
1025
  }
1389
1026
 
1390
- .no-wrap {
1391
- white-space: nowrap;
1027
+ .table-wrap.dragging * {
1028
+ cursor: crosshair !important;
1029
+ user-select: none;
1030
+ }
1031
+
1032
+ .table-wrap > :global(button) {
1033
+ border: 1px solid var(--border-color-primary);
1034
+ border-radius: var(--table-radius);
1035
+ overflow: hidden;
1392
1036
  }
1393
1037
 
1394
1038
  table {
1395
1039
  position: absolute;
1396
1040
  opacity: 0;
1041
+ z-index: -1;
1397
1042
  transition: 150ms;
1398
1043
  width: var(--size-full);
1399
1044
  table-layout: auto;
@@ -1405,159 +1050,39 @@
1405
1050
  border-collapse: separate;
1406
1051
  }
1407
1052
 
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
1053
  thead {
1427
1054
  position: sticky;
1428
1055
  top: 0;
1429
- z-index: var(--layer-2);
1056
+ z-index: 5;
1430
1057
  box-shadow: var(--shadow-drop);
1431
1058
  }
1432
1059
 
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);
1060
+ thead :global(th.pinned-column) {
1061
+ position: sticky;
1062
+ z-index: 6;
1063
+ background: var(--table-even-background-fill) !important;
1485
1064
  }
1486
1065
 
1487
- .sort-buttons {
1488
- display: flex;
1489
- align-items: center;
1490
- flex-shrink: 0;
1491
- order: -1;
1066
+ .dragging {
1067
+ border-color: var(--color-accent);
1492
1068
  }
1493
1069
 
1494
- .editing {
1495
- background: var(--table-editing);
1070
+ .no-wrap {
1071
+ white-space: nowrap;
1496
1072
  }
1497
1073
 
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);
1074
+ div:not(.no-wrap) td {
1075
+ overflow-wrap: anywhere;
1513
1076
  }
1514
1077
 
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);
1078
+ div.no-wrap td {
1079
+ overflow-x: hidden;
1526
1080
  }
1527
1081
 
1528
- .row_odd {
1082
+ .row-odd {
1529
1083
  background: var(--table-odd-background-fill);
1530
1084
  }
1531
1085
 
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
1086
  .header-row {
1562
1087
  display: flex;
1563
1088
  justify-content: flex-end;
@@ -1568,225 +1093,45 @@
1568
1093
  width: 100%;
1569
1094
  }
1570
1095
 
1571
- .label {
1096
+ .header-row .label {
1572
1097
  flex: 1 1 auto;
1573
1098
  margin-right: auto;
1574
1099
  }
1575
1100
 
1576
- .label p {
1101
+ .header-row .label p {
1577
1102
  margin: 0;
1578
1103
  color: var(--block-label-text-color);
1579
1104
  font-size: var(--block-label-text-size);
1580
1105
  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
1106
  position: relative;
1107
+ z-index: 4;
1623
1108
  }
1624
1109
 
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 {
1110
+ .scroll-top-button {
1710
1111
  position: absolute;
1112
+ right: var(--size-4);
1113
+ bottom: var(--size-4);
1114
+ width: var(--size-8);
1115
+ height: var(--size-8);
1116
+ border-radius: var(--table-radius);
1117
+ background: var(--color-accent);
1118
+ color: white;
1119
+ border: none;
1120
+ cursor: pointer;
1711
1121
  display: flex;
1712
1122
  align-items: center;
1713
1123
  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);
1726
- }
1727
-
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);
1733
- }
1734
-
1735
- .table-wrap:not(:focus-within) .selection-button {
1736
- opacity: 0;
1737
- pointer-events: none;
1124
+ font-size: var(--text-lg);
1125
+ z-index: 9;
1126
+ opacity: 0.5;
1738
1127
  }
1739
1128
 
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);
1129
+ .scroll-top-button:hover {
1130
+ opacity: 1;
1763
1131
  }
1764
1132
 
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;
1133
+ tr {
1134
+ border-bottom: 1px solid var(--border-color-primary);
1135
+ text-align: left;
1791
1136
  }
1792
1137
  </style>