@gradio/dataframe 0.17.17 → 0.18.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 +32 -0
- package/Dataframe.stories.svelte +1 -2
- package/Index.svelte +3 -13
- package/dist/Index.svelte +3 -10
- package/dist/Index.svelte.d.ts +1 -5
- package/dist/shared/CellMenu.svelte +37 -0
- package/dist/shared/CellMenu.svelte.d.ts +4 -1
- package/dist/shared/CellMenuIcons.svelte +48 -0
- package/dist/shared/EditableCell.svelte +16 -20
- package/dist/shared/EditableCell.svelte.d.ts +1 -2
- package/dist/shared/FilterMenu.svelte +235 -0
- package/dist/shared/FilterMenu.svelte.d.ts +19 -0
- package/dist/shared/Table.svelte +59 -1
- package/dist/shared/TableCell.svelte.d.ts +1 -1
- package/dist/shared/TableHeader.svelte +26 -0
- package/dist/shared/TableHeader.svelte.d.ts +8 -1
- package/dist/shared/context/dataframe_context.d.ts +20 -1
- package/dist/shared/context/dataframe_context.js +46 -6
- package/dist/shared/types.d.ts +1 -1
- package/dist/shared/utils/data_processing.d.ts +2 -2
- package/dist/shared/utils/filter_utils.d.ts +28 -0
- package/dist/shared/utils/filter_utils.js +123 -0
- package/dist/shared/utils/keyboard_utils.js +5 -1
- package/dist/shared/utils/table_utils.d.ts +7 -0
- package/dist/shared/utils/table_utils.js +29 -0
- package/package.json +10 -10
- package/shared/CellMenu.svelte +45 -1
- package/shared/CellMenuIcons.svelte +48 -0
- package/shared/EditableCell.svelte +18 -25
- package/shared/FilterMenu.svelte +248 -0
- package/shared/Table.svelte +79 -5
- package/shared/TableCell.svelte +1 -1
- package/shared/TableHeader.svelte +32 -1
- package/shared/Toolbar.svelte +1 -1
- package/shared/context/dataframe_context.ts +81 -18
- package/shared/types.ts +1 -1
- package/shared/utils/data_processing.ts +2 -2
- package/shared/utils/filter_utils.ts +207 -0
- package/shared/utils/keyboard_utils.ts +5 -2
- package/shared/utils/table_utils.ts +52 -0
|
@@ -7,6 +7,8 @@
|
|
|
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
|
+
import CellMenuIcons from "./CellMenuIcons.svelte";
|
|
11
|
+
import type { FilterDatatype } from "./context/dataframe_context";
|
|
10
12
|
export let value: string;
|
|
11
13
|
export let i: number;
|
|
12
14
|
export let actual_pinned_columns: number;
|
|
@@ -18,6 +20,12 @@
|
|
|
18
20
|
export let toggle_header_menu: (event: MouseEvent, col: number) => void;
|
|
19
21
|
export let end_header_edit: (event: CustomEvent<KeyboardEvent>) => void;
|
|
20
22
|
export let sort_columns: { col: number; direction: SortDirection }[] = [];
|
|
23
|
+
export let filter_columns: {
|
|
24
|
+
col: number;
|
|
25
|
+
datatype: FilterDatatype;
|
|
26
|
+
filter: string;
|
|
27
|
+
value: string;
|
|
28
|
+
}[] = [];
|
|
21
29
|
|
|
22
30
|
export let latex_delimiters: {
|
|
23
31
|
left: string;
|
|
@@ -28,12 +36,13 @@
|
|
|
28
36
|
export let max_chars: number | undefined;
|
|
29
37
|
export let editable: boolean;
|
|
30
38
|
export let i18n: I18nFormatter;
|
|
31
|
-
export let el:
|
|
39
|
+
export let el: HTMLTextAreaElement | null;
|
|
32
40
|
export let is_static: boolean;
|
|
33
41
|
export let col_count: [number, "fixed" | "dynamic"];
|
|
34
42
|
|
|
35
43
|
$: can_add_columns = col_count && col_count[1] === "dynamic";
|
|
36
44
|
$: sort_index = sort_columns.findIndex((item) => item.col === i);
|
|
45
|
+
$: filter_index = filter_columns.findIndex((item) => item.col === i);
|
|
37
46
|
$: sort_priority = sort_index !== -1 ? sort_index + 1 : null;
|
|
38
47
|
$: current_direction =
|
|
39
48
|
sort_index !== -1 ? sort_columns[sort_index].direction : null;
|
|
@@ -63,6 +72,7 @@
|
|
|
63
72
|
class:last-pinned={i === actual_pinned_columns - 1}
|
|
64
73
|
class:focus={header_edit === i || selected_header === i}
|
|
65
74
|
class:sorted={sort_index !== -1}
|
|
75
|
+
class:filtered={filter_index !== -1}
|
|
66
76
|
aria-sort={get_sort_status(value, sort_columns, headers) === "none"
|
|
67
77
|
? "none"
|
|
68
78
|
: get_sort_status(value, sort_columns, headers) === "asc"
|
|
@@ -125,6 +135,13 @@
|
|
|
125
135
|
{/if}
|
|
126
136
|
</div>
|
|
127
137
|
{/if}
|
|
138
|
+
{#if filter_index !== -1}
|
|
139
|
+
<div class="filter-indicators">
|
|
140
|
+
<span class="filter-icon">
|
|
141
|
+
<CellMenuIcons icon="filter" />
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
{/if}
|
|
128
145
|
</button>
|
|
129
146
|
{#if is_static}
|
|
130
147
|
<Padlock />
|
|
@@ -243,6 +260,20 @@
|
|
|
243
260
|
padding: var(--size-1-5);
|
|
244
261
|
}
|
|
245
262
|
|
|
263
|
+
.filter-indicators {
|
|
264
|
+
display: flex;
|
|
265
|
+
align-items: center;
|
|
266
|
+
margin-left: var(--size-1);
|
|
267
|
+
gap: var(--size-1);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.filter-icon {
|
|
271
|
+
display: flex;
|
|
272
|
+
align-items: center;
|
|
273
|
+
justify-content: center;
|
|
274
|
+
color: var(--body-text-color);
|
|
275
|
+
}
|
|
276
|
+
|
|
246
277
|
.pinned-column {
|
|
247
278
|
position: sticky;
|
|
248
279
|
z-index: 5;
|
package/shared/Toolbar.svelte
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
let input_value = "";
|
|
22
22
|
|
|
23
23
|
function handle_search_input(e: Event): void {
|
|
24
|
-
const target = e.target as
|
|
24
|
+
const target = e.target as HTMLTextAreaElement;
|
|
25
25
|
input_value = target.value;
|
|
26
26
|
const new_query = input_value || null;
|
|
27
27
|
if (current_search_query !== new_query) {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
export const DATAFRAME_KEY = Symbol("dataframe");
|
|
14
14
|
|
|
15
15
|
export type SortDirection = "asc" | "desc";
|
|
16
|
+
export type FilterDatatype = "string" | "number";
|
|
16
17
|
export type CellCoordinate = [number, number];
|
|
17
18
|
|
|
18
19
|
interface DataFrameState {
|
|
@@ -40,6 +41,19 @@ interface DataFrameState {
|
|
|
40
41
|
styling: string[][] | null;
|
|
41
42
|
} | null;
|
|
42
43
|
};
|
|
44
|
+
filter_state: {
|
|
45
|
+
filter_columns: {
|
|
46
|
+
col: number;
|
|
47
|
+
datatype: FilterDatatype;
|
|
48
|
+
filter: string;
|
|
49
|
+
value: string;
|
|
50
|
+
}[];
|
|
51
|
+
initial_data: {
|
|
52
|
+
data: { id: string; value: string | number }[][];
|
|
53
|
+
display_value: string[][] | null;
|
|
54
|
+
styling: string[][] | null;
|
|
55
|
+
} | null;
|
|
56
|
+
};
|
|
43
57
|
ui_state: {
|
|
44
58
|
active_cell_menu: { row: number; col: number; x: number; y: number } | null;
|
|
45
59
|
active_header_menu: { col: number; x: number; y: number } | null;
|
|
@@ -60,6 +74,12 @@ interface DataFrameState {
|
|
|
60
74
|
interface DataFrameActions {
|
|
61
75
|
handle_search: (query: string | null) => void;
|
|
62
76
|
handle_sort: (col: number, direction: SortDirection) => void;
|
|
77
|
+
handle_filter: (
|
|
78
|
+
col: number,
|
|
79
|
+
datatype: FilterDatatype,
|
|
80
|
+
filter: string,
|
|
81
|
+
value: string
|
|
82
|
+
) => void;
|
|
63
83
|
get_sort_status: (name: string, headers: string[]) => "none" | "asc" | "desc";
|
|
64
84
|
sort_data: (
|
|
65
85
|
data: any[][],
|
|
@@ -109,6 +129,7 @@ interface DataFrameActions {
|
|
|
109
129
|
dispatch: (e: "change" | "input", detail?: any) => void
|
|
110
130
|
) => Promise<void>;
|
|
111
131
|
reset_sort_state: () => void;
|
|
132
|
+
reset_filter_state: () => void;
|
|
112
133
|
set_active_cell_menu: (
|
|
113
134
|
menu: { row: number; col: number; x: number; y: number } | null
|
|
114
135
|
) => void;
|
|
@@ -158,7 +179,7 @@ export interface DataFrameContext {
|
|
|
158
179
|
styling?: string[][] | null;
|
|
159
180
|
els?: Record<
|
|
160
181
|
string,
|
|
161
|
-
{ cell: HTMLTableCellElement | null; input:
|
|
182
|
+
{ cell: HTMLTableCellElement | null; input: HTMLTextAreaElement | null }
|
|
162
183
|
>;
|
|
163
184
|
parent_element?: HTMLElement;
|
|
164
185
|
get_data_at?: (row: number, col: number) => string | number;
|
|
@@ -209,6 +230,15 @@ function create_actions(
|
|
|
209
230
|
return { data: new_data, headers: new_headers };
|
|
210
231
|
};
|
|
211
232
|
|
|
233
|
+
const update_array = (
|
|
234
|
+
source: { id: string; value: string | number }[][] | string[][] | null,
|
|
235
|
+
target: any[] | null | undefined
|
|
236
|
+
): void => {
|
|
237
|
+
if (source && target) {
|
|
238
|
+
target.splice(0, target.length, ...JSON.parse(JSON.stringify(source)));
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
212
242
|
return {
|
|
213
243
|
handle_search: (query) =>
|
|
214
244
|
update_state((s) => ({ current_search_query: query })),
|
|
@@ -247,6 +277,39 @@ function create_actions(
|
|
|
247
277
|
}
|
|
248
278
|
};
|
|
249
279
|
}),
|
|
280
|
+
handle_filter: (col, datatype, filter, value) =>
|
|
281
|
+
update_state((s) => {
|
|
282
|
+
const filter_cols = s.filter_state.filter_columns.some(
|
|
283
|
+
(c) => c.col === col
|
|
284
|
+
)
|
|
285
|
+
? s.filter_state.filter_columns.filter((c) => c.col !== col)
|
|
286
|
+
: [
|
|
287
|
+
...s.filter_state.filter_columns,
|
|
288
|
+
{ col, datatype, filter, value }
|
|
289
|
+
];
|
|
290
|
+
|
|
291
|
+
const initial_data =
|
|
292
|
+
s.filter_state.initial_data ||
|
|
293
|
+
(context.data && filter_cols.length > 0
|
|
294
|
+
? {
|
|
295
|
+
data: JSON.parse(JSON.stringify(context.data)),
|
|
296
|
+
display_value: context.display_value
|
|
297
|
+
? JSON.parse(JSON.stringify(context.display_value))
|
|
298
|
+
: null,
|
|
299
|
+
styling: context.styling
|
|
300
|
+
? JSON.parse(JSON.stringify(context.styling))
|
|
301
|
+
: null
|
|
302
|
+
}
|
|
303
|
+
: null);
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
filter_state: {
|
|
307
|
+
...s.filter_state,
|
|
308
|
+
filter_columns: filter_cols,
|
|
309
|
+
initial_data: initial_data
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}),
|
|
250
313
|
get_sort_status: (name, headers) => {
|
|
251
314
|
const s = get(state);
|
|
252
315
|
const sort_item = s.sort_state.sort_columns.find(
|
|
@@ -345,7 +408,8 @@ function create_actions(
|
|
|
345
408
|
) {
|
|
346
409
|
if (!dequal(current_headers, previous_headers)) {
|
|
347
410
|
update_state((s) => ({
|
|
348
|
-
sort_state: { sort_columns: [], row_order: [], initial_data: null }
|
|
411
|
+
sort_state: { sort_columns: [], row_order: [], initial_data: null },
|
|
412
|
+
filter_state: { filter_columns: [], initial_data: null }
|
|
349
413
|
}));
|
|
350
414
|
}
|
|
351
415
|
dispatch("change", {
|
|
@@ -361,22 +425,6 @@ function create_actions(
|
|
|
361
425
|
if (s.sort_state.initial_data && context.data) {
|
|
362
426
|
const original = s.sort_state.initial_data;
|
|
363
427
|
|
|
364
|
-
const update_array = (
|
|
365
|
-
source:
|
|
366
|
-
| { id: string; value: string | number }[][]
|
|
367
|
-
| string[][]
|
|
368
|
-
| null,
|
|
369
|
-
target: any[] | null | undefined
|
|
370
|
-
): void => {
|
|
371
|
-
if (source && target) {
|
|
372
|
-
target.splice(
|
|
373
|
-
0,
|
|
374
|
-
target.length,
|
|
375
|
-
...JSON.parse(JSON.stringify(source))
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
};
|
|
379
|
-
|
|
380
428
|
update_array(original.data, context.data);
|
|
381
429
|
update_array(original.display_value, context.display_value);
|
|
382
430
|
update_array(original.styling, context.styling);
|
|
@@ -386,6 +434,20 @@ function create_actions(
|
|
|
386
434
|
sort_state: { sort_columns: [], row_order: [], initial_data: null }
|
|
387
435
|
};
|
|
388
436
|
}),
|
|
437
|
+
reset_filter_state: () =>
|
|
438
|
+
update_state((s) => {
|
|
439
|
+
if (s.filter_state.initial_data && context.data) {
|
|
440
|
+
const original = s.filter_state.initial_data;
|
|
441
|
+
|
|
442
|
+
update_array(original.data, context.data);
|
|
443
|
+
update_array(original.display_value, context.display_value);
|
|
444
|
+
update_array(original.styling, context.styling);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
filter_state: { filter_columns: [], initial_data: null }
|
|
449
|
+
};
|
|
450
|
+
}),
|
|
389
451
|
set_active_cell_menu: (menu) =>
|
|
390
452
|
update_state((s) => ({
|
|
391
453
|
ui_state: { ...s.ui_state, active_cell_menu: menu }
|
|
@@ -599,6 +661,7 @@ export function create_dataframe_context(
|
|
|
599
661
|
config,
|
|
600
662
|
current_search_query: null,
|
|
601
663
|
sort_state: { sort_columns: [], row_order: [], initial_data: null },
|
|
664
|
+
filter_state: { filter_columns: [], initial_data: null },
|
|
602
665
|
ui_state: {
|
|
603
666
|
active_cell_menu: null,
|
|
604
667
|
active_header_menu: null,
|
package/shared/types.ts
CHANGED
|
@@ -5,7 +5,7 @@ export function make_headers(
|
|
|
5
5
|
col_count: [number, "fixed" | "dynamic"],
|
|
6
6
|
els: Record<
|
|
7
7
|
string,
|
|
8
|
-
{ cell: null | HTMLTableCellElement; input: null |
|
|
8
|
+
{ cell: null | HTMLTableCellElement; input: null | HTMLTextAreaElement }
|
|
9
9
|
>,
|
|
10
10
|
make_id: () => string
|
|
11
11
|
): HeadersWithIDs {
|
|
@@ -38,7 +38,7 @@ export function process_data(
|
|
|
38
38
|
values: (string | number)[][],
|
|
39
39
|
els: Record<
|
|
40
40
|
string,
|
|
41
|
-
{ cell: null | HTMLTableCellElement; input: null |
|
|
41
|
+
{ cell: null | HTMLTableCellElement; input: null | HTMLTextAreaElement }
|
|
42
42
|
>,
|
|
43
43
|
data_binding: Record<string, any>,
|
|
44
44
|
make_id: () => string,
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { filter_table_data } from "./table_utils";
|
|
2
|
+
|
|
3
|
+
export type FilterDatatype = "string" | "number";
|
|
4
|
+
|
|
5
|
+
export function filter_data(
|
|
6
|
+
data: { id: string; value: string | number }[][],
|
|
7
|
+
filter_columns: {
|
|
8
|
+
col: number;
|
|
9
|
+
datatype: FilterDatatype;
|
|
10
|
+
filter: string;
|
|
11
|
+
value: string;
|
|
12
|
+
}[]
|
|
13
|
+
): number[] {
|
|
14
|
+
if (!data || !data.length || !data[0]) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
let row_indices = [...Array(data.length)].map((_, i) => i);
|
|
18
|
+
|
|
19
|
+
if (filter_columns.length > 0) {
|
|
20
|
+
filter_columns.forEach((column) => {
|
|
21
|
+
if (column.datatype === "string") {
|
|
22
|
+
switch (column.filter) {
|
|
23
|
+
case "Contains":
|
|
24
|
+
row_indices = row_indices.filter((i) =>
|
|
25
|
+
data[i][column.col]?.value.toString().includes(column.value)
|
|
26
|
+
);
|
|
27
|
+
break;
|
|
28
|
+
case "Does not contain":
|
|
29
|
+
row_indices = row_indices.filter(
|
|
30
|
+
(i) =>
|
|
31
|
+
!data[i][column.col]?.value.toString().includes(column.value)
|
|
32
|
+
);
|
|
33
|
+
break;
|
|
34
|
+
case "Starts with":
|
|
35
|
+
row_indices = row_indices.filter((i) =>
|
|
36
|
+
data[i][column.col]?.value.toString().startsWith(column.value)
|
|
37
|
+
);
|
|
38
|
+
break;
|
|
39
|
+
case "Ends with":
|
|
40
|
+
row_indices = row_indices.filter((i) =>
|
|
41
|
+
data[i][column.col]?.value.toString().endsWith(column.value)
|
|
42
|
+
);
|
|
43
|
+
break;
|
|
44
|
+
case "Is":
|
|
45
|
+
row_indices = row_indices.filter(
|
|
46
|
+
(i) => data[i][column.col]?.value.toString() === column.value
|
|
47
|
+
);
|
|
48
|
+
break;
|
|
49
|
+
case "Is not":
|
|
50
|
+
row_indices = row_indices.filter(
|
|
51
|
+
(i) => !(data[i][column.col]?.value.toString() === column.value)
|
|
52
|
+
);
|
|
53
|
+
break;
|
|
54
|
+
case "Is empty":
|
|
55
|
+
row_indices = row_indices.filter(
|
|
56
|
+
(i) => data[i][column.col]?.value.toString() === ""
|
|
57
|
+
);
|
|
58
|
+
break;
|
|
59
|
+
case "Is not empty":
|
|
60
|
+
row_indices = row_indices.filter(
|
|
61
|
+
(i) => !(data[i][column.col]?.value.toString() === "")
|
|
62
|
+
);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
} else if (column.datatype === "number") {
|
|
66
|
+
switch (column.filter) {
|
|
67
|
+
case "=":
|
|
68
|
+
row_indices = row_indices.filter((i) => {
|
|
69
|
+
if (
|
|
70
|
+
!isNaN(Number(data[i][column.col]?.value)) &&
|
|
71
|
+
!isNaN(Number(column.value))
|
|
72
|
+
) {
|
|
73
|
+
return (
|
|
74
|
+
Number(data[i][column.col]?.value) === Number(column.value)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
});
|
|
79
|
+
break;
|
|
80
|
+
case "≠":
|
|
81
|
+
row_indices = row_indices.filter((i) => {
|
|
82
|
+
if (
|
|
83
|
+
!isNaN(Number(data[i][column.col]?.value)) &&
|
|
84
|
+
!isNaN(Number(column.value))
|
|
85
|
+
) {
|
|
86
|
+
return !(
|
|
87
|
+
Number(data[i][column.col]?.value) === Number(column.value)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
});
|
|
92
|
+
break;
|
|
93
|
+
case ">":
|
|
94
|
+
row_indices = row_indices.filter((i) => {
|
|
95
|
+
if (
|
|
96
|
+
!isNaN(Number(data[i][column.col]?.value)) &&
|
|
97
|
+
!isNaN(Number(column.value))
|
|
98
|
+
) {
|
|
99
|
+
return (
|
|
100
|
+
Number(data[i][column.col]?.value) > Number(column.value)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
});
|
|
105
|
+
break;
|
|
106
|
+
case "<":
|
|
107
|
+
row_indices = row_indices.filter((i) => {
|
|
108
|
+
if (
|
|
109
|
+
!isNaN(Number(data[i][column.col]?.value)) &&
|
|
110
|
+
!isNaN(Number(column.value))
|
|
111
|
+
) {
|
|
112
|
+
return (
|
|
113
|
+
Number(data[i][column.col]?.value) < Number(column.value)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
});
|
|
118
|
+
break;
|
|
119
|
+
case "≥":
|
|
120
|
+
row_indices = row_indices.filter((i) => {
|
|
121
|
+
if (
|
|
122
|
+
!isNaN(Number(data[i][column.col]?.value)) &&
|
|
123
|
+
!isNaN(Number(column.value))
|
|
124
|
+
) {
|
|
125
|
+
return (
|
|
126
|
+
Number(data[i][column.col]?.value) >= Number(column.value)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
});
|
|
131
|
+
break;
|
|
132
|
+
case "≤":
|
|
133
|
+
row_indices = row_indices.filter((i) => {
|
|
134
|
+
if (
|
|
135
|
+
!isNaN(Number(data[i][column.col]?.value)) &&
|
|
136
|
+
!isNaN(Number(column.value))
|
|
137
|
+
) {
|
|
138
|
+
return (
|
|
139
|
+
Number(data[i][column.col]?.value) <= Number(column.value)
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
});
|
|
144
|
+
break;
|
|
145
|
+
case "Is empty":
|
|
146
|
+
row_indices = row_indices.filter(
|
|
147
|
+
(i) => data[i][column.col]?.value.toString() === ""
|
|
148
|
+
);
|
|
149
|
+
break;
|
|
150
|
+
case "Is not empty":
|
|
151
|
+
row_indices = row_indices.filter((i) => {
|
|
152
|
+
if (!isNaN(Number(data[i][column.col]?.value))) {
|
|
153
|
+
return !(data[i][column.col]?.value.toString() === "");
|
|
154
|
+
}
|
|
155
|
+
return false;
|
|
156
|
+
});
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return row_indices;
|
|
162
|
+
}
|
|
163
|
+
return [...Array(data.length)].map((_, i) => i);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function filter_data_and_preserve_selection(
|
|
167
|
+
data: { id: string; value: string | number }[][],
|
|
168
|
+
display_value: string[][] | null,
|
|
169
|
+
styling: string[][] | null,
|
|
170
|
+
filter_columns: {
|
|
171
|
+
col: number;
|
|
172
|
+
datatype: FilterDatatype;
|
|
173
|
+
filter: string;
|
|
174
|
+
value: string;
|
|
175
|
+
}[],
|
|
176
|
+
selected: [number, number] | false,
|
|
177
|
+
get_current_indices: (
|
|
178
|
+
id: string,
|
|
179
|
+
data: { id: string; value: string | number }[][]
|
|
180
|
+
) => [number, number],
|
|
181
|
+
original_data?: { id: string; value: string | number }[][],
|
|
182
|
+
original_display_value?: string[][] | null,
|
|
183
|
+
original_styling?: string[][] | null
|
|
184
|
+
): { data: typeof data; selected: [number, number] | false } {
|
|
185
|
+
let id = null;
|
|
186
|
+
if (selected && selected[0] in data && selected[1] in data[selected[0]]) {
|
|
187
|
+
id = data[selected[0]][selected[1]].id;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
filter_table_data(
|
|
191
|
+
data,
|
|
192
|
+
display_value,
|
|
193
|
+
styling,
|
|
194
|
+
filter_columns,
|
|
195
|
+
original_data,
|
|
196
|
+
original_display_value,
|
|
197
|
+
original_styling
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
let new_selected = selected;
|
|
201
|
+
if (id) {
|
|
202
|
+
const [i, j] = get_current_indices(id, data);
|
|
203
|
+
new_selected = [i, j];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return { data, selected: new_selected };
|
|
207
|
+
}
|
|
@@ -180,9 +180,10 @@ async function handle_enter_key(
|
|
|
180
180
|
const state = get(ctx.state);
|
|
181
181
|
if (!state.config.editable) return false;
|
|
182
182
|
|
|
183
|
-
event.preventDefault();
|
|
184
|
-
|
|
185
183
|
const editing = state.ui_state.editing;
|
|
184
|
+
if (editing && event.shiftKey) return false;
|
|
185
|
+
|
|
186
|
+
event.preventDefault();
|
|
186
187
|
|
|
187
188
|
if (editing && dequal(editing, [i, j])) {
|
|
188
189
|
const cell_id = ctx.data[i][j].id;
|
|
@@ -191,6 +192,8 @@ async function handle_enter_key(
|
|
|
191
192
|
await save_cell_value(input_el.value, ctx, i, j);
|
|
192
193
|
}
|
|
193
194
|
ctx.actions.set_editing(false);
|
|
195
|
+
await tick();
|
|
196
|
+
ctx.parent_element?.focus();
|
|
194
197
|
} else {
|
|
195
198
|
ctx.actions.set_editing([i, j]);
|
|
196
199
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Headers, HeadersWithIDs, TableCell, TableData } from "../types";
|
|
2
2
|
import { sort_data } from "./sort_utils";
|
|
3
|
+
import { filter_data } from "./filter_utils";
|
|
3
4
|
import type { SortDirection } from "./sort_utils";
|
|
5
|
+
import type { FilterDatatype } from "./filter_utils";
|
|
4
6
|
import { dsvFormat } from "d3-dsv";
|
|
5
7
|
|
|
6
8
|
export function make_cell_id(row: number, col: number): string {
|
|
@@ -49,6 +51,56 @@ export function sort_table_data(
|
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
export function filter_table_data(
|
|
55
|
+
data: TableData,
|
|
56
|
+
display_value: string[][] | null,
|
|
57
|
+
styling: string[][] | null,
|
|
58
|
+
filter_columns: {
|
|
59
|
+
col: number;
|
|
60
|
+
datatype: FilterDatatype;
|
|
61
|
+
filter: string;
|
|
62
|
+
value: string;
|
|
63
|
+
}[],
|
|
64
|
+
original_data?: TableData,
|
|
65
|
+
original_display_value?: string[][] | null,
|
|
66
|
+
original_styling?: string[][] | null
|
|
67
|
+
): void {
|
|
68
|
+
const base_data = original_data ?? data;
|
|
69
|
+
const base_display_value = original_display_value ?? display_value;
|
|
70
|
+
const base_styling = original_styling ?? styling;
|
|
71
|
+
|
|
72
|
+
if (!filter_columns.length) {
|
|
73
|
+
data.splice(0, data.length, ...base_data.map((row) => [...row]));
|
|
74
|
+
if (display_value && base_display_value) {
|
|
75
|
+
display_value.splice(
|
|
76
|
+
0,
|
|
77
|
+
display_value.length,
|
|
78
|
+
...base_display_value.map((row) => [...row])
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (styling && base_styling) {
|
|
82
|
+
styling.splice(0, styling.length, ...base_styling.map((row) => [...row]));
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!data || !data.length) return;
|
|
87
|
+
|
|
88
|
+
const indices = filter_data(base_data, filter_columns);
|
|
89
|
+
|
|
90
|
+
const new_data = indices.map((i: number) => base_data[i]);
|
|
91
|
+
data.splice(0, data.length, ...new_data);
|
|
92
|
+
|
|
93
|
+
if (display_value && base_display_value) {
|
|
94
|
+
const new_display = indices.map((i: number) => base_display_value[i]);
|
|
95
|
+
display_value.splice(0, display_value.length, ...new_display);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (styling && base_styling) {
|
|
99
|
+
const new_styling = indices.map((i: number) => base_styling[i]);
|
|
100
|
+
styling.splice(0, styling.length, ...new_styling);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
52
104
|
export async function copy_table_data(
|
|
53
105
|
data: TableData,
|
|
54
106
|
selected_cells: [number, number][] | null
|