@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.
- package/CHANGELOG.md +34 -0
- package/Dataframe.stories.svelte +202 -9
- package/Index.svelte +7 -13
- package/dist/Index.svelte +5 -9
- package/dist/Index.svelte.d.ts +9 -2
- package/dist/shared/CellMenu.svelte +91 -10
- package/dist/shared/CellMenu.svelte.d.ts +6 -0
- package/dist/shared/CellMenuButton.svelte +45 -0
- package/dist/shared/CellMenuButton.svelte.d.ts +16 -0
- package/dist/shared/CellMenuIcons.svelte +79 -0
- package/dist/shared/EditableCell.svelte +83 -14
- package/dist/shared/EditableCell.svelte.d.ts +12 -3
- package/dist/shared/EmptyRowButton.svelte +28 -0
- package/dist/shared/EmptyRowButton.svelte.d.ts +16 -0
- package/dist/shared/RowNumber.svelte +40 -0
- package/dist/shared/RowNumber.svelte.d.ts +17 -0
- package/dist/shared/Table.svelte +564 -1121
- package/dist/shared/Table.svelte.d.ts +4 -0
- package/dist/shared/TableCell.svelte +291 -0
- package/dist/shared/TableCell.svelte.d.ts +57 -0
- package/dist/shared/TableHeader.svelte +239 -0
- package/dist/shared/TableHeader.svelte.d.ts +45 -0
- package/dist/shared/Toolbar.svelte +18 -8
- package/dist/shared/VirtualTable.svelte +66 -19
- package/dist/shared/VirtualTable.svelte.d.ts +4 -0
- package/dist/shared/context/keyboard_context.d.ts +37 -0
- package/dist/shared/context/keyboard_context.js +12 -0
- package/dist/shared/context/selection_context.d.ts +32 -0
- package/dist/shared/context/selection_context.js +107 -0
- package/dist/shared/context/table_context.d.ts +141 -0
- package/dist/shared/context/table_context.js +375 -0
- package/dist/shared/icons/Padlock.svelte +24 -0
- package/dist/shared/icons/Padlock.svelte.d.ts +23 -0
- package/dist/shared/icons/SelectionButtons.svelte +85 -0
- package/dist/shared/icons/SelectionButtons.svelte.d.ts +18 -0
- package/dist/shared/icons/SortArrowDown.svelte +24 -0
- package/dist/shared/icons/SortArrowDown.svelte.d.ts +16 -0
- package/dist/shared/icons/SortArrowUp.svelte +24 -0
- package/dist/shared/icons/SortArrowUp.svelte.d.ts +16 -0
- package/dist/shared/icons/SortButtonDown.svelte +14 -0
- package/dist/shared/icons/SortButtonDown.svelte.d.ts +23 -0
- package/dist/shared/icons/SortButtonUp.svelte +15 -0
- package/dist/shared/icons/SortButtonUp.svelte.d.ts +23 -0
- package/dist/shared/icons/SortIcon.svelte +46 -68
- package/dist/shared/icons/SortIcon.svelte.d.ts +3 -2
- package/dist/shared/selection_utils.d.ts +2 -1
- package/dist/shared/selection_utils.js +39 -10
- package/dist/shared/utils/data_processing.d.ts +13 -0
- package/dist/shared/utils/data_processing.js +45 -0
- package/dist/shared/utils/drag_utils.d.ts +15 -0
- package/dist/shared/utils/drag_utils.js +57 -0
- package/dist/shared/utils/keyboard_utils.d.ts +2 -0
- package/dist/shared/utils/keyboard_utils.js +186 -0
- package/dist/shared/utils/sort_utils.d.ts +22 -3
- package/dist/shared/utils/sort_utils.js +44 -24
- package/dist/shared/utils/table_utils.d.ts +6 -5
- package/dist/shared/utils/table_utils.js +13 -56
- package/package.json +7 -7
- package/shared/CellMenu.svelte +90 -10
- package/shared/CellMenuButton.svelte +46 -0
- package/shared/CellMenuIcons.svelte +79 -0
- package/shared/EditableCell.svelte +97 -18
- package/shared/EmptyRowButton.svelte +29 -0
- package/shared/RowNumber.svelte +41 -0
- package/shared/Table.svelte +604 -1235
- package/shared/TableCell.svelte +324 -0
- package/shared/TableHeader.svelte +256 -0
- package/shared/Toolbar.svelte +19 -8
- package/shared/VirtualTable.svelte +72 -19
- package/shared/context/keyboard_context.ts +65 -0
- package/shared/context/selection_context.ts +168 -0
- package/shared/context/table_context.ts +625 -0
- package/shared/icons/Padlock.svelte +24 -0
- package/shared/icons/SelectionButtons.svelte +93 -0
- package/shared/icons/SortArrowDown.svelte +25 -0
- package/shared/icons/SortArrowUp.svelte +25 -0
- package/shared/icons/SortButtonDown.svelte +14 -0
- package/shared/icons/SortButtonUp.svelte +15 -0
- package/shared/icons/SortIcon.svelte +47 -70
- package/shared/selection_utils.ts +39 -13
- package/shared/utils/data_processing.ts +72 -0
- package/shared/utils/drag_utils.ts +92 -0
- package/shared/utils/keyboard_utils.ts +238 -0
- package/shared/utils/sort_utils.test.ts +262 -14
- package/shared/utils/sort_utils.ts +67 -31
- package/shared/utils/table_utils.test.ts +66 -45
- package/shared/utils/table_utils.ts +16 -86
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { dequal } from "dequal/lite";
|
|
2
|
+
import { handle_delete_key } from "../selection_utils";
|
|
3
|
+
import type { KeyboardContext } from "../context/keyboard_context";
|
|
4
|
+
import { tick } from "svelte";
|
|
5
|
+
import { copy_table_data } from "./table_utils";
|
|
6
|
+
|
|
7
|
+
function handle_header_navigation(
|
|
8
|
+
event: KeyboardEvent,
|
|
9
|
+
ctx: KeyboardContext
|
|
10
|
+
): boolean {
|
|
11
|
+
if (ctx.selected_header === false || ctx.header_edit !== false) return false;
|
|
12
|
+
switch (event.key) {
|
|
13
|
+
case "ArrowDown":
|
|
14
|
+
ctx.df_actions.set_selected_header(false);
|
|
15
|
+
ctx.df_actions.set_selected([0, ctx.selected_header]);
|
|
16
|
+
ctx.df_actions.set_selected_cells([[0, ctx.selected_header]]);
|
|
17
|
+
return true;
|
|
18
|
+
case "ArrowLeft":
|
|
19
|
+
ctx.df_actions.set_selected_header(
|
|
20
|
+
ctx.selected_header > 0 ? ctx.selected_header - 1 : ctx.selected_header
|
|
21
|
+
);
|
|
22
|
+
return true;
|
|
23
|
+
case "ArrowRight":
|
|
24
|
+
ctx.df_actions.set_selected_header(
|
|
25
|
+
ctx.selected_header < ctx.headers.length - 1
|
|
26
|
+
? ctx.selected_header + 1
|
|
27
|
+
: ctx.selected_header
|
|
28
|
+
);
|
|
29
|
+
return true;
|
|
30
|
+
case "Escape":
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
ctx.df_actions.set_selected_header(false);
|
|
33
|
+
return true;
|
|
34
|
+
case "Enter":
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
if (ctx.editable) {
|
|
37
|
+
ctx.df_actions.set_header_edit(ctx.selected_header);
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function handle_delete_operation(
|
|
45
|
+
event: KeyboardEvent,
|
|
46
|
+
ctx: KeyboardContext
|
|
47
|
+
): boolean {
|
|
48
|
+
if (!ctx.editable) return false;
|
|
49
|
+
if (event.key !== "Delete" && event.key !== "Backspace") return false;
|
|
50
|
+
|
|
51
|
+
if (ctx.editing) {
|
|
52
|
+
const [row, col] = ctx.editing;
|
|
53
|
+
const input_el = ctx.els[ctx.data[row][col].id].input;
|
|
54
|
+
if (input_el && input_el.selectionStart !== input_el.selectionEnd) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (
|
|
58
|
+
event.key === "Delete" &&
|
|
59
|
+
input_el?.selectionStart !== input_el?.value.length
|
|
60
|
+
) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (event.key === "Backspace" && input_el?.selectionStart !== 0) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
if (ctx.selected_cells.length > 0) {
|
|
70
|
+
const new_data = handle_delete_key(ctx.data, ctx.selected_cells);
|
|
71
|
+
ctx.dispatch("change", {
|
|
72
|
+
data: new_data.map((row) => row.map((cell) => cell.value)),
|
|
73
|
+
headers: ctx.headers.map((h) => h.value),
|
|
74
|
+
metadata: null
|
|
75
|
+
});
|
|
76
|
+
ctx.dispatch("input");
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function handle_arrow_keys(
|
|
82
|
+
event: KeyboardEvent,
|
|
83
|
+
ctx: KeyboardContext,
|
|
84
|
+
i: number,
|
|
85
|
+
j: number
|
|
86
|
+
): boolean {
|
|
87
|
+
if (ctx.editing) return false;
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
|
|
90
|
+
const next_coords = ctx.move_cursor(event, [i, j], ctx.data);
|
|
91
|
+
if (next_coords) {
|
|
92
|
+
if (event.shiftKey) {
|
|
93
|
+
ctx.df_actions.set_selected_cells(
|
|
94
|
+
ctx.get_range_selection(
|
|
95
|
+
ctx.selected_cells.length > 0 ? ctx.selected_cells[0] : [i, j],
|
|
96
|
+
next_coords
|
|
97
|
+
)
|
|
98
|
+
);
|
|
99
|
+
ctx.df_actions.set_editing(false);
|
|
100
|
+
} else {
|
|
101
|
+
ctx.df_actions.set_selected_cells([next_coords]);
|
|
102
|
+
ctx.df_actions.set_editing(false);
|
|
103
|
+
}
|
|
104
|
+
ctx.df_actions.set_selected(next_coords);
|
|
105
|
+
} else if (next_coords === false && event.key === "ArrowUp" && i === 0) {
|
|
106
|
+
ctx.df_actions.set_selected_header(j);
|
|
107
|
+
ctx.df_actions.set_selected(false);
|
|
108
|
+
ctx.df_actions.set_selected_cells([]);
|
|
109
|
+
ctx.df_actions.set_editing(false);
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function handle_enter_key(
|
|
115
|
+
event: KeyboardEvent,
|
|
116
|
+
ctx: KeyboardContext,
|
|
117
|
+
i: number,
|
|
118
|
+
j: number
|
|
119
|
+
): Promise<boolean> {
|
|
120
|
+
event.preventDefault();
|
|
121
|
+
if (!ctx.editable) return false;
|
|
122
|
+
|
|
123
|
+
if (event.shiftKey) {
|
|
124
|
+
await ctx.add_row(i);
|
|
125
|
+
await tick();
|
|
126
|
+
ctx.df_actions.set_selected([i + 1, j]);
|
|
127
|
+
} else {
|
|
128
|
+
if (dequal(ctx.editing, [i, j])) {
|
|
129
|
+
const cell_id = ctx.data[i][j].id;
|
|
130
|
+
const input_el = ctx.els[cell_id].input;
|
|
131
|
+
if (input_el) {
|
|
132
|
+
const old_value = ctx.data[i][j].value;
|
|
133
|
+
ctx.data[i][j].value = input_el.value;
|
|
134
|
+
if (old_value !== input_el.value) {
|
|
135
|
+
ctx.dispatch("input");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
ctx.df_actions.set_editing(false);
|
|
139
|
+
await tick();
|
|
140
|
+
ctx.df_actions.set_selected([i, j]);
|
|
141
|
+
} else {
|
|
142
|
+
ctx.df_actions.set_editing([i, j]);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function handle_tab_key(
|
|
149
|
+
event: KeyboardEvent,
|
|
150
|
+
ctx: KeyboardContext,
|
|
151
|
+
i: number,
|
|
152
|
+
j: number
|
|
153
|
+
): boolean {
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
ctx.df_actions.set_editing(false);
|
|
156
|
+
const next_cell = ctx.get_next_cell_coordinates(
|
|
157
|
+
[i, j],
|
|
158
|
+
ctx.data,
|
|
159
|
+
event.shiftKey
|
|
160
|
+
);
|
|
161
|
+
if (next_cell) {
|
|
162
|
+
ctx.df_actions.set_selected_cells([next_cell]);
|
|
163
|
+
ctx.df_actions.set_selected(next_cell);
|
|
164
|
+
if (ctx.editable) {
|
|
165
|
+
ctx.df_actions.set_editing(next_cell);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function handle_default_key(
|
|
172
|
+
event: KeyboardEvent,
|
|
173
|
+
ctx: KeyboardContext,
|
|
174
|
+
i: number,
|
|
175
|
+
j: number
|
|
176
|
+
): boolean {
|
|
177
|
+
if (!ctx.editable) return false;
|
|
178
|
+
if (
|
|
179
|
+
(!ctx.editing || (ctx.editing && dequal(ctx.editing, [i, j]))) &&
|
|
180
|
+
event.key.length === 1
|
|
181
|
+
) {
|
|
182
|
+
ctx.df_actions.set_editing([i, j]);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function handle_cell_navigation(
|
|
189
|
+
event: KeyboardEvent,
|
|
190
|
+
ctx: KeyboardContext
|
|
191
|
+
): Promise<boolean> {
|
|
192
|
+
if (!ctx.selected) return false;
|
|
193
|
+
if (event.key === "c" && (event.metaKey || event.ctrlKey)) {
|
|
194
|
+
event.preventDefault();
|
|
195
|
+
if (ctx.selected_cells.length > 0) {
|
|
196
|
+
await copy_table_data(ctx.data, ctx.selected_cells);
|
|
197
|
+
}
|
|
198
|
+
ctx.set_copy_flash(true);
|
|
199
|
+
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const [i, j] = ctx.selected;
|
|
204
|
+
|
|
205
|
+
switch (event.key) {
|
|
206
|
+
case "ArrowRight":
|
|
207
|
+
case "ArrowLeft":
|
|
208
|
+
case "ArrowDown":
|
|
209
|
+
case "ArrowUp":
|
|
210
|
+
return handle_arrow_keys(event, ctx, i, j);
|
|
211
|
+
case "Escape":
|
|
212
|
+
if (!ctx.editable) return false;
|
|
213
|
+
event.preventDefault();
|
|
214
|
+
ctx.df_actions.set_editing(false);
|
|
215
|
+
tick().then(() => {
|
|
216
|
+
if (ctx.parent_element) {
|
|
217
|
+
ctx.parent_element.focus();
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return true;
|
|
222
|
+
case "Enter":
|
|
223
|
+
return await handle_enter_key(event, ctx, i, j);
|
|
224
|
+
case "Tab":
|
|
225
|
+
return handle_tab_key(event, ctx, i, j);
|
|
226
|
+
default:
|
|
227
|
+
return handle_default_key(event, ctx, i, j);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function handle_keydown(
|
|
232
|
+
event: KeyboardEvent,
|
|
233
|
+
context: KeyboardContext
|
|
234
|
+
): Promise<void> {
|
|
235
|
+
if (handle_header_navigation(event, context)) return;
|
|
236
|
+
if (handle_delete_operation(event, context)) return;
|
|
237
|
+
await handle_cell_navigation(event, context);
|
|
238
|
+
}
|
|
@@ -1,24 +1,89 @@
|
|
|
1
1
|
import { describe, test, expect } from "vitest";
|
|
2
|
-
import { get_sort_status, sort_data } from "./sort_utils";
|
|
2
|
+
import { get_sort_status, sort_data, SortDirection } from "./sort_utils";
|
|
3
3
|
|
|
4
4
|
describe("sort_utils", () => {
|
|
5
5
|
describe("get_sort_status", () => {
|
|
6
6
|
const headers = ["A", "B", "C"];
|
|
7
7
|
|
|
8
8
|
test("returns none when no sort is active", () => {
|
|
9
|
-
expect(get_sort_status("A",
|
|
9
|
+
expect(get_sort_status("A", [], headers)).toBe("none");
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
test("returns ascending when column is sorted ascending", () => {
|
|
13
|
-
expect(
|
|
13
|
+
expect(
|
|
14
|
+
get_sort_status(
|
|
15
|
+
"A",
|
|
16
|
+
[{ col: 0, direction: "asc" as SortDirection }],
|
|
17
|
+
headers
|
|
18
|
+
)
|
|
19
|
+
).toBe("asc");
|
|
14
20
|
});
|
|
15
21
|
|
|
16
22
|
test("returns descending when column is sorted descending", () => {
|
|
17
|
-
expect(
|
|
23
|
+
expect(
|
|
24
|
+
get_sort_status(
|
|
25
|
+
"B",
|
|
26
|
+
[{ col: 1, direction: "desc" as SortDirection }],
|
|
27
|
+
headers
|
|
28
|
+
)
|
|
29
|
+
).toBe("desc");
|
|
18
30
|
});
|
|
19
31
|
|
|
20
32
|
test("returns none for non-matching column", () => {
|
|
21
|
-
expect(
|
|
33
|
+
expect(
|
|
34
|
+
get_sort_status(
|
|
35
|
+
"A",
|
|
36
|
+
[{ col: 1, direction: "asc" as SortDirection }],
|
|
37
|
+
headers
|
|
38
|
+
)
|
|
39
|
+
).toBe("none");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("handles multiple sort columns", () => {
|
|
43
|
+
const sort_columns = [
|
|
44
|
+
{ col: 0, direction: "asc" as SortDirection },
|
|
45
|
+
{ col: 1, direction: "desc" as SortDirection }
|
|
46
|
+
];
|
|
47
|
+
expect(get_sort_status("A", sort_columns, headers)).toBe("asc");
|
|
48
|
+
expect(get_sort_status("B", sort_columns, headers)).toBe("desc");
|
|
49
|
+
expect(get_sort_status("C", sort_columns, headers)).toBe("none");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("handles invalid column indices", () => {
|
|
53
|
+
expect(
|
|
54
|
+
get_sort_status(
|
|
55
|
+
"A",
|
|
56
|
+
[{ col: -1, direction: "asc" as SortDirection }],
|
|
57
|
+
headers
|
|
58
|
+
)
|
|
59
|
+
).toBe("none");
|
|
60
|
+
|
|
61
|
+
expect(
|
|
62
|
+
get_sort_status(
|
|
63
|
+
"A",
|
|
64
|
+
[{ col: 999, direction: "asc" as SortDirection }],
|
|
65
|
+
headers
|
|
66
|
+
)
|
|
67
|
+
).toBe("none");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("handles empty headers", () => {
|
|
71
|
+
expect(
|
|
72
|
+
get_sort_status(
|
|
73
|
+
"A",
|
|
74
|
+
[{ col: 0, direction: "asc" as SortDirection }],
|
|
75
|
+
[]
|
|
76
|
+
)
|
|
77
|
+
).toBe("none");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("prioritizes first matching column in sort_columns", () => {
|
|
81
|
+
const sort_columns = [
|
|
82
|
+
{ col: 0, direction: "asc" as SortDirection },
|
|
83
|
+
{ col: 0, direction: "desc" as SortDirection }
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
expect(get_sort_status("A", sort_columns, headers)).toBe("asc");
|
|
22
87
|
});
|
|
23
88
|
});
|
|
24
89
|
|
|
@@ -39,33 +104,216 @@ describe("sort_utils", () => {
|
|
|
39
104
|
];
|
|
40
105
|
|
|
41
106
|
test("sorts strings ascending", () => {
|
|
42
|
-
const indices = sort_data(data,
|
|
107
|
+
const indices = sort_data(data, [
|
|
108
|
+
{ col: 0, direction: "asc" as SortDirection }
|
|
109
|
+
]);
|
|
43
110
|
expect(indices).toEqual([1, 0, 2]); // A, B, C
|
|
44
111
|
});
|
|
45
112
|
|
|
46
113
|
test("sorts numbers ascending", () => {
|
|
47
|
-
const indices = sort_data(data,
|
|
48
|
-
|
|
114
|
+
const indices = sort_data(data, [
|
|
115
|
+
{ col: 1, direction: "asc" as SortDirection }
|
|
116
|
+
]);
|
|
117
|
+
expect(indices).toEqual([1, 0, 2]);
|
|
49
118
|
});
|
|
50
119
|
|
|
51
120
|
test("sorts strings descending", () => {
|
|
52
|
-
const indices = sort_data(data,
|
|
53
|
-
|
|
121
|
+
const indices = sort_data(data, [
|
|
122
|
+
{ col: 0, direction: "desc" as SortDirection }
|
|
123
|
+
]);
|
|
124
|
+
expect(indices).toEqual([2, 0, 1]);
|
|
54
125
|
});
|
|
55
126
|
|
|
56
|
-
test("returns original order when sort params are
|
|
57
|
-
const indices = sort_data(data,
|
|
127
|
+
test("returns original order when sort params are empty", () => {
|
|
128
|
+
const indices = sort_data(data, []);
|
|
58
129
|
expect(indices).toEqual([0, 1, 2]);
|
|
59
130
|
});
|
|
60
131
|
|
|
61
132
|
test("handles empty data", () => {
|
|
62
|
-
const indices = sort_data(
|
|
133
|
+
const indices = sort_data(
|
|
134
|
+
[],
|
|
135
|
+
[{ col: 0, direction: "asc" as SortDirection }]
|
|
136
|
+
);
|
|
63
137
|
expect(indices).toEqual([]);
|
|
64
138
|
});
|
|
65
139
|
|
|
66
140
|
test("handles invalid column index", () => {
|
|
67
|
-
const indices = sort_data(data,
|
|
141
|
+
const indices = sort_data(data, [
|
|
142
|
+
{ col: 999, direction: "asc" as SortDirection }
|
|
143
|
+
]);
|
|
144
|
+
expect(indices).toEqual([0, 1, 2]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("sorts by multiple columns", () => {
|
|
148
|
+
const test_data = [
|
|
149
|
+
[
|
|
150
|
+
{ id: "1", value: "A" },
|
|
151
|
+
{ id: "2", value: 2 }
|
|
152
|
+
],
|
|
153
|
+
[
|
|
154
|
+
{ id: "3", value: "A" },
|
|
155
|
+
{ id: "4", value: 1 }
|
|
156
|
+
],
|
|
157
|
+
[
|
|
158
|
+
{ id: "5", value: "B" },
|
|
159
|
+
{ id: "6", value: 3 }
|
|
160
|
+
]
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
const indices = sort_data(test_data, [
|
|
164
|
+
{ col: 0, direction: "asc" as SortDirection },
|
|
165
|
+
{ col: 1, direction: "asc" as SortDirection }
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
expect(indices).toEqual([1, 0, 2]);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("respects sort direction for each column", () => {
|
|
172
|
+
const test_data = [
|
|
173
|
+
[
|
|
174
|
+
{ id: "1", value: "A" },
|
|
175
|
+
{ id: "2", value: 2 }
|
|
176
|
+
],
|
|
177
|
+
[
|
|
178
|
+
{ id: "3", value: "A" },
|
|
179
|
+
{ id: "4", value: 1 }
|
|
180
|
+
],
|
|
181
|
+
[
|
|
182
|
+
{ id: "5", value: "B" },
|
|
183
|
+
{ id: "6", value: 3 }
|
|
184
|
+
]
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
const indices = sort_data(test_data, [
|
|
188
|
+
{ col: 0, direction: "asc" as SortDirection },
|
|
189
|
+
{ col: 1, direction: "desc" as SortDirection }
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
expect(indices).toEqual([0, 1, 2]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("handles mixed data types in sort columns", () => {
|
|
196
|
+
const mixed_data = [
|
|
197
|
+
[
|
|
198
|
+
{ id: "1", value: "A" },
|
|
199
|
+
{ id: "2", value: 2 }
|
|
200
|
+
],
|
|
201
|
+
[
|
|
202
|
+
{ id: "3", value: "A" },
|
|
203
|
+
{ id: "4", value: 1 }
|
|
204
|
+
],
|
|
205
|
+
[
|
|
206
|
+
{ id: "5", value: "B" },
|
|
207
|
+
{ id: "6", value: 2 }
|
|
208
|
+
]
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
const indices = sort_data(mixed_data, [
|
|
212
|
+
{ col: 0, direction: "asc" as SortDirection },
|
|
213
|
+
{ col: 1, direction: "asc" as SortDirection }
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
expect(indices).toEqual([1, 0, 2]);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("handles more than two sort columns", () => {
|
|
220
|
+
const complex_data = [
|
|
221
|
+
[
|
|
222
|
+
{ id: "1", value: "A" },
|
|
223
|
+
{ id: "2", value: 1 },
|
|
224
|
+
{ id: "3", value: "X" }
|
|
225
|
+
],
|
|
226
|
+
[
|
|
227
|
+
{ id: "4", value: "A" },
|
|
228
|
+
{ id: "5", value: 1 },
|
|
229
|
+
{ id: "6", value: "Y" }
|
|
230
|
+
],
|
|
231
|
+
[
|
|
232
|
+
{ id: "7", value: "B" },
|
|
233
|
+
{ id: "8", value: 2 },
|
|
234
|
+
{ id: "9", value: "Z" }
|
|
235
|
+
]
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
const indices = sort_data(complex_data, [
|
|
239
|
+
{ col: 0, direction: "asc" as SortDirection },
|
|
240
|
+
{ col: 1, direction: "asc" as SortDirection },
|
|
241
|
+
{ col: 2, direction: "asc" as SortDirection }
|
|
242
|
+
]);
|
|
243
|
+
|
|
244
|
+
expect(indices).toEqual([0, 1, 2]);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("ignores invalid sort columns", () => {
|
|
248
|
+
const indices = sort_data(data, [
|
|
249
|
+
{ col: -1, direction: "asc" as SortDirection },
|
|
250
|
+
{ col: 0, direction: "asc" as SortDirection }
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
expect(indices).toEqual([1, 0, 2]);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("maintains original order when all values are equal", () => {
|
|
257
|
+
const equal_data = [
|
|
258
|
+
[
|
|
259
|
+
{ id: "1", value: "A" },
|
|
260
|
+
{ id: "2", value: 1 }
|
|
261
|
+
],
|
|
262
|
+
[
|
|
263
|
+
{ id: "3", value: "A" },
|
|
264
|
+
{ id: "4", value: 1 }
|
|
265
|
+
],
|
|
266
|
+
[
|
|
267
|
+
{ id: "5", value: "A" },
|
|
268
|
+
{ id: "6", value: 1 }
|
|
269
|
+
]
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
const indices = sort_data(equal_data, [
|
|
273
|
+
{ col: 0, direction: "asc" as SortDirection },
|
|
274
|
+
{ col: 1, direction: "asc" as SortDirection }
|
|
275
|
+
]);
|
|
276
|
+
|
|
68
277
|
expect(indices).toEqual([0, 1, 2]);
|
|
69
278
|
});
|
|
279
|
+
|
|
280
|
+
test("handles undefined values in data rows", () => {
|
|
281
|
+
const data_with_undefined = [
|
|
282
|
+
[
|
|
283
|
+
{ id: "1", value: "A" },
|
|
284
|
+
{ id: "2", value: "" }
|
|
285
|
+
],
|
|
286
|
+
[
|
|
287
|
+
{ id: "3", value: "B" },
|
|
288
|
+
{ id: "4", value: 2 }
|
|
289
|
+
]
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
const indices = sort_data(data_with_undefined, [
|
|
293
|
+
{ col: 0, direction: "asc" as SortDirection },
|
|
294
|
+
{ col: 1, direction: "asc" as SortDirection }
|
|
295
|
+
]);
|
|
296
|
+
|
|
297
|
+
expect(indices).toEqual([0, 1]);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("handles missing values in data", () => {
|
|
301
|
+
const data_with_missing = [
|
|
302
|
+
[
|
|
303
|
+
{ id: "1", value: "" },
|
|
304
|
+
{ id: "2", value: 1 }
|
|
305
|
+
],
|
|
306
|
+
[
|
|
307
|
+
{ id: "3", value: "A" },
|
|
308
|
+
{ id: "4", value: 2 }
|
|
309
|
+
]
|
|
310
|
+
];
|
|
311
|
+
|
|
312
|
+
const indices = sort_data(data_with_missing, [
|
|
313
|
+
{ col: 0, direction: "asc" as SortDirection }
|
|
314
|
+
]);
|
|
315
|
+
|
|
316
|
+
expect(indices).toEqual([0, 1]);
|
|
317
|
+
});
|
|
70
318
|
});
|
|
71
319
|
});
|
|
@@ -1,55 +1,91 @@
|
|
|
1
1
|
import type { Headers } from "../types";
|
|
2
|
+
import { sort_table_data } from "./table_utils";
|
|
2
3
|
|
|
3
|
-
export type SortDirection = "asc" | "
|
|
4
|
+
export type SortDirection = "asc" | "desc";
|
|
4
5
|
|
|
5
6
|
export function get_sort_status(
|
|
6
7
|
name: string,
|
|
7
|
-
|
|
8
|
-
direction: SortDirection | undefined,
|
|
8
|
+
sort_columns: { col: number; direction: SortDirection }[],
|
|
9
9
|
headers: Headers
|
|
10
|
-
): "none" | "
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
): "none" | "asc" | "desc" {
|
|
11
|
+
if (!sort_columns.length) return "none";
|
|
12
|
+
|
|
13
|
+
const sort_item = sort_columns.find((item) => {
|
|
14
|
+
const col = item.col;
|
|
15
|
+
if (col < 0 || col >= headers.length) return false;
|
|
16
|
+
return headers[col] === name;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (!sort_item) return "none";
|
|
20
|
+
return sort_item.direction;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
export function sort_data(
|
|
21
24
|
data: { id: string; value: string | number }[][],
|
|
22
|
-
|
|
23
|
-
sort_direction: SortDirection | undefined
|
|
25
|
+
sort_columns: { col: number; direction: SortDirection }[]
|
|
24
26
|
): number[] {
|
|
25
27
|
if (!data || !data.length || !data[0]) {
|
|
26
28
|
return [];
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
if (
|
|
30
|
-
typeof sort_by === "number" &&
|
|
31
|
-
sort_direction &&
|
|
32
|
-
sort_by >= 0 &&
|
|
33
|
-
sort_by < data[0].length
|
|
34
|
-
) {
|
|
31
|
+
if (sort_columns.length > 0) {
|
|
35
32
|
const row_indices = [...Array(data.length)].map((_, i) => i);
|
|
36
33
|
row_indices.sort((row_a_idx, row_b_idx) => {
|
|
37
34
|
const row_a = data[row_a_idx];
|
|
38
35
|
const row_b = data[row_b_idx];
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
36
|
+
|
|
37
|
+
for (const { col: sort_by, direction } of sort_columns) {
|
|
38
|
+
if (
|
|
39
|
+
!row_a ||
|
|
40
|
+
!row_b ||
|
|
41
|
+
sort_by < 0 ||
|
|
42
|
+
sort_by >= row_a.length ||
|
|
43
|
+
sort_by >= row_b.length ||
|
|
44
|
+
!row_a[sort_by] ||
|
|
45
|
+
!row_b[sort_by]
|
|
46
|
+
) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const val_a = row_a[sort_by].value;
|
|
51
|
+
const val_b = row_b[sort_by].value;
|
|
52
|
+
const comparison = val_a < val_b ? -1 : val_a > val_b ? 1 : 0;
|
|
53
|
+
|
|
54
|
+
if (comparison !== 0) {
|
|
55
|
+
return direction === "asc" ? comparison : -comparison;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return 0;
|
|
51
60
|
});
|
|
52
61
|
return row_indices;
|
|
53
62
|
}
|
|
54
63
|
return [...Array(data.length)].map((_, i) => i);
|
|
55
64
|
}
|
|
65
|
+
|
|
66
|
+
export function sort_data_and_preserve_selection(
|
|
67
|
+
data: { id: string; value: string | number }[][],
|
|
68
|
+
display_value: string[][] | null,
|
|
69
|
+
styling: string[][] | null,
|
|
70
|
+
sort_columns: { col: number; direction: SortDirection }[],
|
|
71
|
+
selected: [number, number] | false,
|
|
72
|
+
get_current_indices: (
|
|
73
|
+
id: string,
|
|
74
|
+
data: { id: string; value: string | number }[][]
|
|
75
|
+
) => [number, number]
|
|
76
|
+
): { data: typeof data; selected: [number, number] | false } {
|
|
77
|
+
let id = null;
|
|
78
|
+
if (selected && selected[0] in data && selected[1] in data[selected[0]]) {
|
|
79
|
+
id = data[selected[0]][selected[1]].id;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
sort_table_data(data, display_value, styling, sort_columns);
|
|
83
|
+
|
|
84
|
+
let new_selected = selected;
|
|
85
|
+
if (id) {
|
|
86
|
+
const [i, j] = get_current_indices(id, data);
|
|
87
|
+
new_selected = [i, j];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { data, selected: new_selected };
|
|
91
|
+
}
|