@gradio/dataframe 0.17.4 → 0.17.6

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 (38) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/Dataframe.stories.svelte +29 -0
  3. package/dist/shared/CellMenu.svelte.d.ts +1 -1
  4. package/dist/shared/EditableCell.svelte +8 -17
  5. package/dist/shared/EditableCell.svelte.d.ts +5 -3
  6. package/dist/shared/Table.svelte +111 -138
  7. package/dist/shared/TableCell.svelte +4 -6
  8. package/dist/shared/TableCell.svelte.d.ts +5 -1
  9. package/dist/shared/TableHeader.svelte +4 -3
  10. package/dist/shared/TableHeader.svelte.d.ts +1 -2
  11. package/dist/shared/context/dataframe_context.d.ts +147 -0
  12. package/dist/shared/context/dataframe_context.js +335 -0
  13. package/dist/shared/selection_utils.d.ts +1 -2
  14. package/dist/shared/selection_utils.js +0 -13
  15. package/dist/shared/utils/drag_utils.js +1 -0
  16. package/dist/shared/utils/keyboard_utils.d.ts +3 -2
  17. package/dist/shared/utils/keyboard_utils.js +107 -68
  18. package/package.json +6 -6
  19. package/shared/CellMenu.svelte +1 -1
  20. package/shared/EditableCell.svelte +9 -20
  21. package/shared/Table.svelte +147 -165
  22. package/shared/TableCell.svelte +9 -6
  23. package/shared/TableHeader.svelte +5 -8
  24. package/shared/context/dataframe_context.ts +576 -0
  25. package/shared/selection_utils.ts +1 -23
  26. package/shared/utils/drag_utils.ts +1 -0
  27. package/shared/utils/keyboard_utils.ts +142 -80
  28. package/{shared/utils → test}/sort_utils.test.ts +5 -1
  29. package/{shared/utils → test}/table_utils.test.ts +2 -2
  30. package/dist/shared/context/keyboard_context.d.ts +0 -37
  31. package/dist/shared/context/keyboard_context.js +0 -12
  32. package/dist/shared/context/selection_context.d.ts +0 -32
  33. package/dist/shared/context/selection_context.js +0 -107
  34. package/dist/shared/context/table_context.d.ts +0 -141
  35. package/dist/shared/context/table_context.js +0 -375
  36. package/shared/context/keyboard_context.ts +0 -65
  37. package/shared/context/selection_context.ts +0 -168
  38. package/shared/context/table_context.ts +0 -625
@@ -1,10 +1,8 @@
1
1
  <script lang="ts" context="module">
2
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";
3
+ create_dataframe_context,
4
+ type SortDirection
5
+ } from "./context/dataframe_context";
8
6
  </script>
9
7
 
10
8
  <script lang="ts">
@@ -28,9 +26,6 @@
28
26
  import {
29
27
  is_cell_selected,
30
28
  should_show_cell_menu,
31
- get_next_cell_coordinates,
32
- get_range_selection,
33
- move_cursor,
34
29
  get_current_indices,
35
30
  handle_click_outside as handle_click_outside_util,
36
31
  calculate_selection_positions
@@ -41,7 +36,7 @@
41
36
  handle_file_upload
42
37
  } from "./utils/table_utils";
43
38
  import { make_headers, process_data } from "./utils/data_processing";
44
- import { handle_keydown } from "./utils/keyboard_utils";
39
+ import { handle_keydown, handle_cell_blur } from "./utils/keyboard_utils";
45
40
  import {
46
41
  create_drag_handlers,
47
42
  type DragState,
@@ -82,12 +77,7 @@
82
77
  export let pinned_columns = 0;
83
78
  export let static_columns: (string | number)[] = [];
84
79
 
85
- $: actual_pinned_columns =
86
- pinned_columns && data?.[0]?.length
87
- ? Math.min(pinned_columns, data[0].length)
88
- : 0;
89
-
90
- const { state: df_state, actions: df_actions } = create_dataframe_context({
80
+ const df_ctx = create_dataframe_context({
91
81
  show_fullscreen_button,
92
82
  show_copy_button,
93
83
  show_search,
@@ -102,23 +92,57 @@
102
92
  max_chars
103
93
  });
104
94
 
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
- }
95
+ const { state: df_state, actions: df_actions } = df_ctx;
96
+
97
+ $: selected_cells = $df_state.ui_state.selected_cells;
113
98
  $: selected = $df_state.ui_state.selected;
99
+ $: editing = $df_state.ui_state.editing;
100
+ $: header_edit = $df_state.ui_state.header_edit;
101
+ $: selected_header = $df_state.ui_state.selected_header;
102
+ $: active_cell_menu = $df_state.ui_state.active_cell_menu;
103
+ $: active_header_menu = $df_state.ui_state.active_header_menu;
104
+ $: copy_flash = $df_state.ui_state.copy_flash;
114
105
 
115
- export let display_value: string[][] | null = null;
116
- export let styling: string[][] | null = null;
117
- let els: Record<
118
- string,
119
- { cell: null | HTMLTableCellElement; input: null | HTMLInputElement }
120
- > = {};
121
- let data_binding: Record<string, (typeof data)[0][0]> = {};
106
+ $: actual_pinned_columns =
107
+ pinned_columns && data?.[0]?.length
108
+ ? Math.min(pinned_columns, data[0].length)
109
+ : 0;
110
+
111
+ onMount(() => {
112
+ df_ctx.parent_element = parent;
113
+ df_ctx.get_data_at = get_data_at;
114
+ df_ctx.dispatch = dispatch;
115
+ init_drag_handlers();
116
+
117
+ const observer = new IntersectionObserver((entries) => {
118
+ entries.forEach((entry) => {
119
+ if (entry.isIntersecting && !is_visible) {
120
+ set_cell_widths();
121
+ }
122
+ is_visible = entry.isIntersecting;
123
+ });
124
+ });
125
+ observer.observe(parent);
126
+ document.addEventListener("click", handle_click_outside);
127
+ window.addEventListener("resize", handle_resize);
128
+ document.addEventListener("fullscreenchange", handle_fullscreen_change);
129
+
130
+ return () => {
131
+ observer.disconnect();
132
+ document.removeEventListener("click", handle_click_outside);
133
+ window.removeEventListener("resize", handle_resize);
134
+ document.removeEventListener(
135
+ "fullscreenchange",
136
+ handle_fullscreen_change
137
+ );
138
+ };
139
+ });
140
+
141
+ $: if (data || _headers || els) {
142
+ df_ctx.data = data;
143
+ df_ctx.headers = _headers;
144
+ df_ctx.els = els;
145
+ }
122
146
 
123
147
  const dispatch = createEventDispatcher<{
124
148
  change: DataframeValue;
@@ -127,17 +151,27 @@
127
151
  search: string | null;
128
152
  }>();
129
153
 
130
- $: editing = $df_state.ui_state.editing;
131
- let clear_on_focus = false;
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;
154
+ let els: Record<
155
+ string,
156
+ { cell: null | HTMLTableCellElement; input: null | HTMLInputElement }
157
+ > = {};
158
+ let data_binding: Record<string, (typeof data)[0][0]> = {};
159
+ let _headers = make_headers(headers, col_count, els, make_id);
160
+ let old_headers: string[] = headers;
161
+ let data: { id: string; value: string | number; display_value?: string }[][] =
162
+ [[]];
163
+ let old_val: undefined | (string | number)[][] = undefined;
164
+ let search_results: {
165
+ id: string;
166
+ value: string | number;
167
+ display_value?: string;
168
+ styling?: string;
169
+ }[][] = [[]];
136
170
  let is_fullscreen = false;
137
171
  let dragging = false;
138
- let copy_flash = false;
139
-
140
172
  let color_accent_copied: string;
173
+ let filtered_to_original_map: number[] = [];
174
+
141
175
  onMount(() => {
142
176
  const color = getComputedStyle(document.documentElement)
143
177
  .getPropertyValue("--color-accent")
@@ -152,13 +186,6 @@
152
186
  const get_data_at = (row: number, col: number): string | number =>
153
187
  data?.[row]?.[col]?.value;
154
188
 
155
- function make_id(): string {
156
- return Math.random().toString(36).substring(2, 15);
157
- }
158
-
159
- let _headers = make_headers(headers, col_count, els, make_id);
160
- let old_headers: string[] = headers;
161
-
162
189
  $: {
163
190
  if (!dequal(headers, old_headers)) {
164
191
  _headers = make_headers(headers, col_count, els, make_id);
@@ -166,14 +193,12 @@
166
193
  }
167
194
  }
168
195
 
169
- let data: { id: string; value: string | number }[][] = [[]];
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
- }[][] = [[]];
196
+ function make_id(): string {
197
+ return Math.random().toString(36).substring(2, 15);
198
+ }
199
+
200
+ export let display_value: string[][] | null = null;
201
+ export let styling: string[][] | null = null;
177
202
 
178
203
  $: if (!dequal(values, old_val)) {
179
204
  if (parent) {
@@ -228,11 +253,22 @@
228
253
 
229
254
  $: if ($df_state.current_search_query !== undefined) {
230
255
  const cell_map = new Map();
256
+ filtered_to_original_map = [];
231
257
 
232
258
  data.forEach((row, row_idx) => {
259
+ if (
260
+ row.some((cell) =>
261
+ String(cell?.value)
262
+ .toLowerCase()
263
+ .includes($df_state.current_search_query?.toLowerCase() || "")
264
+ )
265
+ ) {
266
+ filtered_to_original_map.push(row_idx);
267
+ }
233
268
  row.forEach((cell, col_idx) => {
234
269
  cell_map.set(cell.id, {
235
270
  value: cell.value,
271
+ display_value: cell.display_value || String(cell.value),
236
272
  styling: styling?.[row_idx]?.[col_idx] || ""
237
273
  });
238
274
  });
@@ -250,6 +286,8 @@
250
286
  };
251
287
  })
252
288
  );
289
+ } else {
290
+ filtered_to_original_map = [];
253
291
  }
254
292
 
255
293
  let previous_headers = _headers.map((h) => h.value);
@@ -280,22 +318,13 @@
280
318
  sort_data(data, display_value, styling);
281
319
  }
282
320
 
283
- $: {
284
- df_actions.sort_data(data, display_value, styling);
321
+ $: if ($df_state.sort_state.sort_columns.length > 0) {
322
+ sort_data(data, display_value, styling);
285
323
  df_actions.update_row_order(data);
286
324
  }
287
325
 
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);
292
- }
293
- }
294
- }
295
-
296
326
  async function edit_header(i: number, _select = false): Promise<void> {
297
- if (!editable || header_edit === i) return;
298
- if (!editable || col_count[1] !== "dynamic" || header_edit === i) return;
327
+ if (!editable || header_edit === i || col_count[1] !== "dynamic") return;
299
328
  df_actions.set_header_edit(i);
300
329
  }
301
330
 
@@ -306,7 +335,6 @@
306
335
  event.preventDefault();
307
336
  event.stopPropagation();
308
337
  if (!editable) return;
309
- clear_on_focus = false;
310
338
  df_actions.set_editing(false);
311
339
  df_actions.handle_header_click(col, editable);
312
340
  parent.focus();
@@ -339,7 +367,6 @@
339
367
  data.push(new_row);
340
368
  }
341
369
 
342
- data = data;
343
370
  selected = [index !== undefined ? index : data.length - 1, 0];
344
371
  }
345
372
 
@@ -454,98 +481,34 @@
454
481
  selected = result.selected;
455
482
  }
456
483
 
457
- $: sort_data(data, display_value, styling);
458
-
459
484
  $: selected_index = !!selected && selected[0];
460
485
 
461
486
  let is_visible = false;
462
487
 
463
- onMount(() => {
464
- const observer = new IntersectionObserver((entries) => {
465
- entries.forEach((entry) => {
466
- if (entry.isIntersecting && !is_visible) {
467
- set_cell_widths();
468
- data = data;
469
- }
470
- is_visible = entry.isIntersecting;
471
- });
472
- });
473
-
474
- observer.observe(parent);
475
- document.addEventListener("click", handle_click_outside);
476
- window.addEventListener("resize", handle_resize);
477
- document.addEventListener("fullscreenchange", handle_fullscreen_change);
478
-
479
- return () => {
480
- observer.disconnect();
481
- document.removeEventListener("click", handle_click_outside);
482
- window.removeEventListener("resize", handle_resize);
483
- document.removeEventListener(
484
- "fullscreenchange",
485
- handle_fullscreen_change
486
- );
487
- };
488
- });
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
- }
488
+ const set_copy_flash = (value: boolean): void => {
489
+ df_actions.set_copy_flash(value);
490
+ if (value) {
491
+ setTimeout(() => df_actions.set_copy_flash(false), 800);
515
492
  }
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
-
531
- function handle_cell_click(
532
- event: MouseEvent,
533
- row: number,
534
- col: number
535
- ): void {
536
- selection_ctx.actions.handle_cell_click(event, row, col);
537
- }
493
+ };
538
494
 
539
- function toggle_cell_menu(event: MouseEvent, row: number, col: number): void {
540
- selection_ctx.actions.toggle_cell_menu(event, row, col);
541
- }
495
+ let previous_selected_cells: [number, number][] = [];
542
496
 
543
- function handle_select_column(col: number): void {
544
- selection_ctx.actions.handle_select_column(col);
497
+ $: {
498
+ if (copy_flash && !dequal(selected_cells, previous_selected_cells)) {
499
+ set_copy_flash(false);
500
+ }
501
+ previous_selected_cells = selected_cells;
545
502
  }
546
503
 
547
- function handle_select_row(row: number): void {
548
- selection_ctx.actions.handle_select_row(row);
504
+ function handle_blur(
505
+ event: CustomEvent<{
506
+ blur_event: FocusEvent;
507
+ coords: [number, number];
508
+ }>
509
+ ): void {
510
+ const { blur_event, coords } = event.detail;
511
+ handle_cell_blur(blur_event, df_ctx, coords);
549
512
  }
550
513
 
551
514
  function toggle_fullscreen(): void {
@@ -604,8 +567,8 @@
604
567
  df_actions.set_active_header_menu(null);
605
568
  }
606
569
 
607
- let coords: CellCoordinate;
608
- $: if (selected !== false) coords = selected;
570
+ let selected_cell_coords: CellCoordinate;
571
+ $: if (selected !== false) selected_cell_coords = selected;
609
572
 
610
573
  $: if (selected !== false) {
611
574
  const positions = calculate_selection_positions(
@@ -726,8 +689,7 @@
726
689
  (value) => (is_dragging = value),
727
690
  (cells) => df_actions.set_selected_cells(cells),
728
691
  (cell) => df_actions.set_selected(cell),
729
- (event, row, col) =>
730
- selection_ctx.actions.handle_cell_click(event, row, col),
692
+ (event, row, col) => df_actions.handle_cell_click(event, row, col),
731
693
  show_row_numbers,
732
694
  parent
733
695
  );
@@ -738,6 +700,23 @@
738
700
  $: handle_mouse_down = drag_handlers?.handle_mouse_down || (() => {});
739
701
  $: handle_mouse_move = drag_handlers?.handle_mouse_move || (() => {});
740
702
  $: handle_mouse_up = drag_handlers?.handle_mouse_up || (() => {});
703
+
704
+ function get_cell_display_value(row: number, col: number): string {
705
+ const is_search_active = $df_state.current_search_query !== undefined;
706
+
707
+ if (is_search_active && search_results?.[row]?.[col]) {
708
+ return (
709
+ search_results[row][col].display_value ||
710
+ String(search_results[row][col].value)
711
+ );
712
+ }
713
+
714
+ if (data?.[row]?.[col]) {
715
+ return data[row][col].display_value || String(data[row][col].value);
716
+ }
717
+
718
+ return "";
719
+ }
741
720
  </script>
742
721
 
743
722
  <svelte:window on:resize={() => set_cell_widths()} />
@@ -770,7 +749,7 @@
770
749
  class:no-wrap={!wrap}
771
750
  style="height:{table_height}px;"
772
751
  class:menu-open={active_cell_menu || active_header_menu}
773
- on:keydown={(e) => handle_keydown(e, keyboard_ctx)}
752
+ on:keydown={(e) => handle_keydown(e, df_ctx)}
774
753
  on:mousemove={handle_mouse_move}
775
754
  on:mouseup={handle_mouse_up}
776
755
  on:mouseleave={handle_mouse_up}
@@ -793,7 +772,6 @@
793
772
  {actual_pinned_columns}
794
773
  {header_edit}
795
774
  {selected_header}
796
- get_sort_status={df_actions.get_sort_status}
797
775
  {headers}
798
776
  {get_cell_width}
799
777
  {handle_header_click}
@@ -834,10 +812,11 @@
834
812
  show_selection_buttons={selected_cells.length === 1 &&
835
813
  selected_cells[0][0] === 0 &&
836
814
  selected_cells[0][1] === j}
837
- coords={[0, j]}
838
- on_select_column={handle_select_column}
839
- on_select_row={handle_select_row}
815
+ coords={selected_cell_coords}
816
+ on_select_column={df_actions.handle_select_column}
817
+ on_select_row={df_actions.handle_select_row}
840
818
  {is_dragging}
819
+ on:blur={handle_blur}
841
820
  />
842
821
  </div>
843
822
  </td>
@@ -899,7 +878,6 @@
899
878
  {actual_pinned_columns}
900
879
  {header_edit}
901
880
  {selected_header}
902
- get_sort_status={df_actions.get_sort_status}
903
881
  {headers}
904
882
  {get_cell_width}
905
883
  {handle_header_click}
@@ -925,12 +903,17 @@
925
903
  {#each item as { value, id }, j (id)}
926
904
  <TableCell
927
905
  bind:value={search_results[index][j].value}
928
- {index}
906
+ display_value={get_cell_display_value(index, j)}
907
+ index={$df_state.current_search_query !== undefined &&
908
+ filtered_to_original_map[index] !== undefined
909
+ ? filtered_to_original_map[index]
910
+ : index}
929
911
  {j}
930
912
  {actual_pinned_columns}
931
913
  {get_cell_width}
932
914
  handle_cell_click={(e, r, c) => handle_mouse_down(e, r, c)}
933
- {toggle_cell_menu}
915
+ {handle_blur}
916
+ toggle_cell_menu={df_actions.toggle_cell_menu}
934
917
  {is_cell_selected}
935
918
  {should_show_cell_menu}
936
919
  {selected_cells}
@@ -941,15 +924,14 @@
941
924
  {line_breaks}
942
925
  datatype={Array.isArray(datatype) ? datatype[j] : datatype}
943
926
  {editing}
944
- {clear_on_focus}
945
927
  {max_chars}
946
928
  {root}
947
929
  {editable}
948
930
  is_static={static_columns.includes(j)}
949
931
  {i18n}
950
932
  {components}
951
- {handle_select_column}
952
- {handle_select_row}
933
+ handle_select_column={df_actions.handle_select_column}
934
+ handle_select_row={df_actions.handle_select_row}
953
935
  bind:el={els[id]}
954
936
  {is_dragging}
955
937
  {wrap}
@@ -15,6 +15,12 @@
15
15
  row: number,
16
16
  col: number
17
17
  ) => void;
18
+ export let handle_blur: (
19
+ event: CustomEvent<{
20
+ blur_event: FocusEvent;
21
+ coords: [number, number];
22
+ }>
23
+ ) => void;
18
24
  export let toggle_cell_menu: (
19
25
  event: MouseEvent,
20
26
  row: number,
@@ -46,7 +52,6 @@
46
52
  export let line_breaks: boolean;
47
53
  export let datatype: Datatype;
48
54
  export let editing: [number, number] | false;
49
- export let clear_on_focus: boolean;
50
55
  export let max_chars: number | undefined;
51
56
  export let root: string;
52
57
  export let editable: boolean;
@@ -60,6 +65,7 @@
60
65
  export let handle_select_column: (col: number) => void;
61
66
  export let handle_select_row: (row: number) => void;
62
67
  export let is_dragging: boolean;
68
+ export let display_value: string | undefined;
63
69
  export let wrap = false;
64
70
 
65
71
  function get_cell_position(col_index: number): string {
@@ -116,16 +122,13 @@
116
122
  <EditableCell
117
123
  bind:value
118
124
  bind:el={el.input}
119
- display_value={String(value)}
125
+ display_value={display_value || String(value)}
120
126
  {latex_delimiters}
121
127
  {line_breaks}
122
128
  {editable}
123
129
  {is_static}
124
130
  edit={editing && editing[0] === index && editing[1] === j}
125
131
  {datatype}
126
- on:blur={() => {
127
- clear_on_focus = false;
128
- }}
129
132
  on:focus={() => {
130
133
  const row = index;
131
134
  const col = j;
@@ -133,7 +136,7 @@
133
136
  selected_cells = [[row, col]];
134
137
  }
135
138
  }}
136
- {clear_on_focus}
139
+ on:blur={handle_blur}
137
140
  {root}
138
141
  {max_chars}
139
142
  {i18n}
@@ -2,20 +2,16 @@
2
2
  import EditableCell from "./EditableCell.svelte";
3
3
  import CellMenuButton from "./CellMenuButton.svelte";
4
4
  import type { I18nFormatter } from "js/core/src/gradio_helper";
5
- import type { SortDirection } from "./context/table_context";
5
+ import { get_sort_status } from "./utils/sort_utils";
6
6
  import Padlock from "./icons/Padlock.svelte";
7
7
  import SortArrowUp from "./icons/SortArrowUp.svelte";
8
8
  import SortArrowDown from "./icons/SortArrowDown.svelte";
9
-
9
+ import type { SortDirection } from "./context/dataframe_context";
10
10
  export let value: string;
11
11
  export let i: number;
12
12
  export let actual_pinned_columns: number;
13
13
  export let header_edit: number | false;
14
14
  export let selected_header: number | false;
15
- export let get_sort_status: (
16
- value: string,
17
- headers: string[]
18
- ) => "none" | "asc" | "desc";
19
15
  export let headers: string[];
20
16
  export let get_cell_width: (index: number) => string;
21
17
  export let handle_header_click: (event: MouseEvent, col: number) => void;
@@ -68,9 +64,9 @@
68
64
  class:last-pinned={i === actual_pinned_columns - 1}
69
65
  class:focus={header_edit === i || selected_header === i}
70
66
  class:sorted={sort_index !== -1}
71
- aria-sort={get_sort_status(value, headers) === "none"
67
+ aria-sort={get_sort_status(value, sort_columns, headers) === "none"
72
68
  ? "none"
73
- : get_sort_status(value, headers) === "asc"
69
+ : get_sort_status(value, sort_columns, headers) === "asc"
74
70
  ? "ascending"
75
71
  : "descending"}
76
72
  style="width: {get_cell_width(i)}; left: {get_header_position(i)};"
@@ -113,6 +109,7 @@
113
109
  {editable}
114
110
  {is_static}
115
111
  {i18n}
112
+ coords={[i, 0]}
116
113
  />
117
114
  {#if sort_index !== -1}
118
115
  <div class="sort-indicators">