@gradio/dataframe 0.17.17 → 0.18.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.
- package/CHANGELOG.md +16 -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/FilterMenu.svelte +235 -0
- package/dist/shared/FilterMenu.svelte.d.ts +19 -0
- package/dist/shared/Table.svelte +59 -1
- package/dist/shared/TableHeader.svelte +26 -0
- package/dist/shared/TableHeader.svelte.d.ts +7 -0
- package/dist/shared/context/dataframe_context.d.ts +19 -0
- package/dist/shared/context/dataframe_context.js +46 -6
- package/dist/shared/utils/filter_utils.d.ts +28 -0
- package/dist/shared/utils/filter_utils.js +123 -0
- package/dist/shared/utils/table_utils.d.ts +7 -0
- package/dist/shared/utils/table_utils.js +29 -0
- package/package.json +7 -7
- package/shared/CellMenu.svelte +45 -1
- package/shared/CellMenuIcons.svelte +48 -0
- package/shared/FilterMenu.svelte +248 -0
- package/shared/Table.svelte +78 -4
- package/shared/TableHeader.svelte +31 -0
- package/shared/context/dataframe_context.ts +80 -17
- package/shared/utils/filter_utils.ts +207 -0
- package/shared/utils/table_utils.ts +52 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import { Check } from "@gradio/icons";
|
|
4
|
+
import DropdownArrow from "../../icons/src/DropdownArrow.svelte";
|
|
5
|
+
import type { FilterDatatype } from "./context/dataframe_context";
|
|
6
|
+
|
|
7
|
+
export let on_filter: (
|
|
8
|
+
datatype: FilterDatatype,
|
|
9
|
+
selected_filter: string,
|
|
10
|
+
value: string
|
|
11
|
+
) => void = () => {};
|
|
12
|
+
|
|
13
|
+
let menu_element: HTMLDivElement;
|
|
14
|
+
let datatype: "string" | "number" = "string";
|
|
15
|
+
let current_filter = "Contains";
|
|
16
|
+
let filter_dropdown_open = false;
|
|
17
|
+
let filter_input_value = "";
|
|
18
|
+
|
|
19
|
+
const filter_options = {
|
|
20
|
+
string: [
|
|
21
|
+
"Contains",
|
|
22
|
+
"Does not contain",
|
|
23
|
+
"Starts with",
|
|
24
|
+
"Ends with",
|
|
25
|
+
"Is",
|
|
26
|
+
"Is not",
|
|
27
|
+
"Is empty",
|
|
28
|
+
"Is not empty"
|
|
29
|
+
],
|
|
30
|
+
number: ["=", "≠", ">", "<", "≥", "≤", "Is empty", "Is not empty"]
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
onMount(() => {
|
|
34
|
+
position_menu();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function position_menu(): void {
|
|
38
|
+
if (!menu_element) return;
|
|
39
|
+
|
|
40
|
+
const viewport_width = window.innerWidth;
|
|
41
|
+
const viewport_height = window.innerHeight;
|
|
42
|
+
const menu_rect = menu_element.getBoundingClientRect();
|
|
43
|
+
|
|
44
|
+
const x = (viewport_width - menu_rect.width) / 2;
|
|
45
|
+
const y = (viewport_height - menu_rect.height) / 2;
|
|
46
|
+
|
|
47
|
+
menu_element.style.left = `${x}px`;
|
|
48
|
+
menu_element.style.top = `${y}px`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function handle_filter_input(e: Event): void {
|
|
52
|
+
const target = e.target as HTMLInputElement;
|
|
53
|
+
filter_input_value = target.value;
|
|
54
|
+
}
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<div>
|
|
58
|
+
<div class="background"></div>
|
|
59
|
+
<div bind:this={menu_element} class="filter-menu">
|
|
60
|
+
<div class="filter-datatype-container">
|
|
61
|
+
<span>Filter as</span>
|
|
62
|
+
<button
|
|
63
|
+
on:click|stopPropagation={() => {
|
|
64
|
+
datatype = datatype === "string" ? "number" : "string";
|
|
65
|
+
current_filter = filter_options[datatype][0];
|
|
66
|
+
}}
|
|
67
|
+
aria-label={`Change filter type. Filtering ${datatype}s`}
|
|
68
|
+
>
|
|
69
|
+
{datatype}
|
|
70
|
+
</button>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div class="input-container">
|
|
74
|
+
<div class="filter-dropdown">
|
|
75
|
+
<button
|
|
76
|
+
on:click|stopPropagation={() =>
|
|
77
|
+
(filter_dropdown_open = !filter_dropdown_open)}
|
|
78
|
+
aria-label={`Change filter. Using '${current_filter}'`}
|
|
79
|
+
>
|
|
80
|
+
{current_filter}
|
|
81
|
+
<DropdownArrow />
|
|
82
|
+
</button>
|
|
83
|
+
|
|
84
|
+
{#if filter_dropdown_open}
|
|
85
|
+
<div class="dropdown-filter-options">
|
|
86
|
+
{#each filter_options[datatype] as opt}
|
|
87
|
+
<button
|
|
88
|
+
on:click|stopPropagation={() => {
|
|
89
|
+
current_filter = opt;
|
|
90
|
+
filter_dropdown_open = !filter_dropdown_open;
|
|
91
|
+
}}
|
|
92
|
+
class="filter-option"
|
|
93
|
+
>
|
|
94
|
+
{opt}
|
|
95
|
+
</button>
|
|
96
|
+
{/each}
|
|
97
|
+
</div>
|
|
98
|
+
{/if}
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<input
|
|
102
|
+
type="text"
|
|
103
|
+
value={filter_input_value}
|
|
104
|
+
on:click|stopPropagation
|
|
105
|
+
on:input={handle_filter_input}
|
|
106
|
+
placeholder="Type a value"
|
|
107
|
+
class="filter-input"
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<button
|
|
112
|
+
class="check-button"
|
|
113
|
+
on:click={() => on_filter(datatype, current_filter, filter_input_value)}
|
|
114
|
+
>
|
|
115
|
+
<Check />
|
|
116
|
+
</button>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<style>
|
|
121
|
+
.background {
|
|
122
|
+
position: fixed;
|
|
123
|
+
top: 0;
|
|
124
|
+
left: 0;
|
|
125
|
+
width: 100vw;
|
|
126
|
+
height: 100vh;
|
|
127
|
+
background-color: rgba(0, 0, 0, 0.4);
|
|
128
|
+
z-index: 20;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.filter-menu {
|
|
132
|
+
position: fixed;
|
|
133
|
+
background: var(--background-fill-primary);
|
|
134
|
+
border: 1px solid var(--border-color-primary);
|
|
135
|
+
border-radius: var(--radius-sm);
|
|
136
|
+
padding: var(--size-2);
|
|
137
|
+
display: flex;
|
|
138
|
+
flex-direction: column;
|
|
139
|
+
gap: var(--size-2);
|
|
140
|
+
box-shadow: var(--shadow-drop-lg);
|
|
141
|
+
width: 300px;
|
|
142
|
+
z-index: 21;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.filter-datatype-container {
|
|
146
|
+
display: flex;
|
|
147
|
+
gap: var(--size-2);
|
|
148
|
+
align-items: center;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.filter-menu span {
|
|
152
|
+
font-size: var(--text-sm);
|
|
153
|
+
color: var(--body-text-color);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.filter-menu button {
|
|
157
|
+
height: var(--size-6);
|
|
158
|
+
background: none;
|
|
159
|
+
border: 1px solid var(--border-color-primary);
|
|
160
|
+
border-radius: var(--radius-sm);
|
|
161
|
+
padding: var(--size-1) var(--size-2);
|
|
162
|
+
color: var(--body-text-color);
|
|
163
|
+
font-size: var(--text-sm);
|
|
164
|
+
cursor: pointer;
|
|
165
|
+
display: flex;
|
|
166
|
+
align-items: center;
|
|
167
|
+
justify-content: center;
|
|
168
|
+
gap: var(--size-2);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.filter-menu button:hover {
|
|
172
|
+
background-color: var(--background-fill-secondary);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.filter-input {
|
|
176
|
+
width: var(--size-full);
|
|
177
|
+
height: var(--size-6);
|
|
178
|
+
padding: var(--size-2);
|
|
179
|
+
padding-right: var(--size-8);
|
|
180
|
+
border: 1px solid var(--border-color-primary);
|
|
181
|
+
border-radius: var(--table-radius);
|
|
182
|
+
font-size: var(--text-sm);
|
|
183
|
+
color: var(--body-text-color);
|
|
184
|
+
background: var(--background-fill-secondary);
|
|
185
|
+
transition: all 0.2s ease;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.filter-input:hover {
|
|
189
|
+
border-color: var(--border-color-secondary);
|
|
190
|
+
background: var(--background-fill-primary);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.filter-input:focus {
|
|
194
|
+
outline: none;
|
|
195
|
+
border-color: var(--color-accent);
|
|
196
|
+
background: var(--background-fill-primary);
|
|
197
|
+
box-shadow: 0 0 0 1px var(--color-accent);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.dropdown-filter-options {
|
|
201
|
+
display: flex;
|
|
202
|
+
flex-direction: column;
|
|
203
|
+
background: var(--background-fill-primary);
|
|
204
|
+
border: 1px solid var(--border-color-primary);
|
|
205
|
+
border-radius: var(--radius-sm);
|
|
206
|
+
box-shadow: var(--shadow-drop-md);
|
|
207
|
+
position: absolute;
|
|
208
|
+
z-index: var(--layer-1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.dropdown-filter-options .filter-option {
|
|
212
|
+
border: none;
|
|
213
|
+
justify-content: flex-start;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.input-container {
|
|
217
|
+
display: flex;
|
|
218
|
+
gap: var(--size-2);
|
|
219
|
+
align-items: center;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.input-container button {
|
|
223
|
+
width: 130px;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
:global(svg.dropdown-arrow) {
|
|
227
|
+
width: var(--size-4);
|
|
228
|
+
height: var(--size-4);
|
|
229
|
+
margin-left: auto;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.filter-menu .check-button {
|
|
233
|
+
background: var(--color-accent);
|
|
234
|
+
color: white;
|
|
235
|
+
border: none;
|
|
236
|
+
width: var(--size-full);
|
|
237
|
+
height: var(--size-6);
|
|
238
|
+
border-radius: var(--radius-sm);
|
|
239
|
+
display: flex;
|
|
240
|
+
align-items: center;
|
|
241
|
+
justify-content: center;
|
|
242
|
+
padding: var(--size-1);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.check-button:hover {
|
|
246
|
+
background: var(--color-accent-soft);
|
|
247
|
+
}
|
|
248
|
+
</style>
|
package/shared/Table.svelte
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script lang="ts" context="module">
|
|
2
2
|
import {
|
|
3
3
|
create_dataframe_context,
|
|
4
|
-
type SortDirection
|
|
4
|
+
type SortDirection,
|
|
5
|
+
type FilterDatatype
|
|
5
6
|
} from "./context/dataframe_context";
|
|
6
7
|
</script>
|
|
7
8
|
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
type DragHandlers
|
|
44
45
|
} from "./utils/drag_utils";
|
|
45
46
|
import { sort_data_and_preserve_selection } from "./utils/sort_utils";
|
|
47
|
+
import { filter_data_and_preserve_selection } from "./utils/filter_utils";
|
|
46
48
|
|
|
47
49
|
export let datatype: Datatype | Datatype[];
|
|
48
50
|
export let label: string | null = null;
|
|
@@ -261,6 +263,12 @@
|
|
|
261
263
|
df_actions.reset_sort_state();
|
|
262
264
|
}
|
|
263
265
|
|
|
266
|
+
if ($df_state.filter_state.filter_columns.length > 0) {
|
|
267
|
+
filter_data(data, display_value, styling);
|
|
268
|
+
} else {
|
|
269
|
+
df_actions.reset_filter_state();
|
|
270
|
+
}
|
|
271
|
+
|
|
264
272
|
if ($df_state.current_search_query) {
|
|
265
273
|
df_actions.handle_search(null);
|
|
266
274
|
}
|
|
@@ -340,11 +348,33 @@
|
|
|
340
348
|
|
|
341
349
|
function clear_sort(): void {
|
|
342
350
|
df_actions.reset_sort_state();
|
|
351
|
+
sort_data(data, display_value, styling);
|
|
343
352
|
}
|
|
344
353
|
|
|
345
|
-
$:
|
|
346
|
-
|
|
347
|
-
|
|
354
|
+
$: {
|
|
355
|
+
if ($df_state.filter_state.filter_columns.length > 0) {
|
|
356
|
+
filter_data(data, display_value, styling);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if ($df_state.sort_state.sort_columns.length > 0) {
|
|
360
|
+
sort_data(data, display_value, styling);
|
|
361
|
+
df_actions.update_row_order(data);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function handle_filter(
|
|
366
|
+
col: number,
|
|
367
|
+
datatype: FilterDatatype,
|
|
368
|
+
filter: string,
|
|
369
|
+
value: string
|
|
370
|
+
): void {
|
|
371
|
+
df_actions.handle_filter(col, datatype, filter, value);
|
|
372
|
+
filter_data(data, display_value, styling);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function clear_filter(): void {
|
|
376
|
+
df_actions.reset_filter_state();
|
|
377
|
+
filter_data(data, display_value, styling);
|
|
348
378
|
}
|
|
349
379
|
|
|
350
380
|
async function edit_header(i: number, _select = false): Promise<void> {
|
|
@@ -449,6 +479,9 @@
|
|
|
449
479
|
|
|
450
480
|
function set_cell_widths(): void {
|
|
451
481
|
const column_count = data[0]?.length || 0;
|
|
482
|
+
if ($df_state.filter_state.filter_columns.length > 0) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
452
485
|
if (
|
|
453
486
|
last_width_data_length === data.length &&
|
|
454
487
|
last_width_column_count === column_count &&
|
|
@@ -513,6 +546,26 @@
|
|
|
513
546
|
selected = result.selected;
|
|
514
547
|
}
|
|
515
548
|
|
|
549
|
+
function filter_data(
|
|
550
|
+
_data: typeof data,
|
|
551
|
+
_display_value: string[][] | null,
|
|
552
|
+
_styling: string[][] | null
|
|
553
|
+
): void {
|
|
554
|
+
const result = filter_data_and_preserve_selection(
|
|
555
|
+
_data,
|
|
556
|
+
_display_value,
|
|
557
|
+
_styling,
|
|
558
|
+
$df_state.filter_state.filter_columns,
|
|
559
|
+
selected,
|
|
560
|
+
get_current_indices,
|
|
561
|
+
$df_state.filter_state.initial_data?.data,
|
|
562
|
+
$df_state.filter_state.initial_data?.display_value,
|
|
563
|
+
$df_state.filter_state.initial_data?.styling
|
|
564
|
+
);
|
|
565
|
+
data = result.data;
|
|
566
|
+
selected = result.selected;
|
|
567
|
+
}
|
|
568
|
+
|
|
516
569
|
$: selected_index = !!selected && selected[0];
|
|
517
570
|
|
|
518
571
|
let is_visible = false;
|
|
@@ -802,6 +855,7 @@
|
|
|
802
855
|
{toggle_header_menu}
|
|
803
856
|
{end_header_edit}
|
|
804
857
|
sort_columns={$df_state.sort_state.sort_columns}
|
|
858
|
+
filter_columns={$df_state.filter_state.filter_columns}
|
|
805
859
|
{latex_delimiters}
|
|
806
860
|
{line_breaks}
|
|
807
861
|
{max_chars}
|
|
@@ -906,6 +960,7 @@
|
|
|
906
960
|
{toggle_header_menu}
|
|
907
961
|
{end_header_edit}
|
|
908
962
|
sort_columns={$df_state.sort_state.sort_columns}
|
|
963
|
+
filter_columns={$df_state.filter_state.filter_columns}
|
|
909
964
|
{latex_delimiters}
|
|
910
965
|
{line_breaks}
|
|
911
966
|
{max_chars}
|
|
@@ -1022,6 +1077,25 @@
|
|
|
1022
1077
|
(item) => item.col === (active_header_menu?.col ?? -1)
|
|
1023
1078
|
) + 1 || null
|
|
1024
1079
|
: null}
|
|
1080
|
+
on_filter={active_header_menu
|
|
1081
|
+
? (datatype, filter, value) => {
|
|
1082
|
+
if (active_header_menu) {
|
|
1083
|
+
handle_filter(active_header_menu.col, datatype, filter, value);
|
|
1084
|
+
df_actions.set_active_header_menu(null);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
: undefined}
|
|
1088
|
+
on_clear_filter={active_header_menu
|
|
1089
|
+
? () => {
|
|
1090
|
+
clear_filter();
|
|
1091
|
+
df_actions.set_active_header_menu(null);
|
|
1092
|
+
}
|
|
1093
|
+
: undefined}
|
|
1094
|
+
filter_active={active_header_menu
|
|
1095
|
+
? $df_state.filter_state.filter_columns.some(
|
|
1096
|
+
(c) => c.col === (active_header_menu?.col ?? -1)
|
|
1097
|
+
)
|
|
1098
|
+
: null}
|
|
1025
1099
|
/>
|
|
1026
1100
|
{/if}
|
|
1027
1101
|
|
|
@@ -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;
|
|
@@ -34,6 +42,7 @@
|
|
|
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;
|
|
@@ -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;
|
|
@@ -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,
|