@gradio/dataframe 0.17.16 → 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 +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 +0 -2
- package/dist/shared/EditableCell.svelte.d.ts +0 -1
- package/dist/shared/FilterMenu.svelte +235 -0
- package/dist/shared/FilterMenu.svelte.d.ts +19 -0
- package/dist/shared/Table.svelte +59 -5
- package/dist/shared/TableCell.svelte +0 -2
- package/dist/shared/TableCell.svelte.d.ts +0 -1
- package/dist/shared/TableHeader.svelte +26 -2
- package/dist/shared/TableHeader.svelte.d.ts +7 -1
- 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 +9 -9
- package/shared/CellMenu.svelte +45 -1
- package/shared/CellMenuIcons.svelte +48 -0
- package/shared/EditableCell.svelte +0 -2
- package/shared/FilterMenu.svelte +248 -0
- package/shared/Table.svelte +78 -8
- package/shared/TableCell.svelte +0 -2
- package/shared/TableHeader.svelte +31 -2
- package/shared/context/dataframe_context.ts +80 -17
- package/shared/utils/filter_utils.ts +207 -0
- package/shared/utils/table_utils.ts +52 -0
package/shared/CellMenu.svelte
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from "svelte";
|
|
3
3
|
import CellMenuIcons from "./CellMenuIcons.svelte";
|
|
4
|
+
import FilterMenu from "./FilterMenu.svelte";
|
|
4
5
|
import type { I18nFormatter } from "js/utils/src";
|
|
5
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
SortDirection,
|
|
8
|
+
FilterDatatype
|
|
9
|
+
} from "./context/dataframe_context";
|
|
6
10
|
|
|
7
11
|
export let x: number;
|
|
8
12
|
export let y: number;
|
|
@@ -21,10 +25,18 @@
|
|
|
21
25
|
export let on_clear_sort: () => void = () => {};
|
|
22
26
|
export let sort_direction: SortDirection | null = null;
|
|
23
27
|
export let sort_priority: number | null = null;
|
|
28
|
+
export let on_filter: (
|
|
29
|
+
datatype: FilterDatatype,
|
|
30
|
+
selected_filter: string,
|
|
31
|
+
value: string
|
|
32
|
+
) => void = () => {};
|
|
33
|
+
export let on_clear_filter: () => void = () => {};
|
|
34
|
+
export let filter_active: boolean | null = null;
|
|
24
35
|
export let editable = true;
|
|
25
36
|
|
|
26
37
|
export let i18n: I18nFormatter;
|
|
27
38
|
let menu_element: HTMLDivElement;
|
|
39
|
+
let active_filter_menu: { x: number; y: number } | null = null;
|
|
28
40
|
|
|
29
41
|
$: is_header = row === -1;
|
|
30
42
|
$: can_add_rows = editable && row_count[1] === "dynamic";
|
|
@@ -55,6 +67,19 @@
|
|
|
55
67
|
menu_element.style.left = `${new_x}px`;
|
|
56
68
|
menu_element.style.top = `${new_y}px`;
|
|
57
69
|
}
|
|
70
|
+
|
|
71
|
+
function toggle_filter_menu(): void {
|
|
72
|
+
if (filter_active) {
|
|
73
|
+
on_filter("string", "", "");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const menu_rect = menu_element.getBoundingClientRect();
|
|
78
|
+
active_filter_menu = {
|
|
79
|
+
x: menu_rect.right,
|
|
80
|
+
y: menu_rect.top + menu_rect.height / 2
|
|
81
|
+
};
|
|
82
|
+
}
|
|
58
83
|
</script>
|
|
59
84
|
|
|
60
85
|
<div bind:this={menu_element} class="cell-menu" role="menu">
|
|
@@ -85,6 +110,21 @@
|
|
|
85
110
|
<CellMenuIcons icon="clear-sort" />
|
|
86
111
|
{i18n("dataframe.clear_sort")}
|
|
87
112
|
</button>
|
|
113
|
+
<button
|
|
114
|
+
role="menuitem"
|
|
115
|
+
on:click|stopPropagation={toggle_filter_menu}
|
|
116
|
+
class:active={filter_active || active_filter_menu}
|
|
117
|
+
>
|
|
118
|
+
<CellMenuIcons icon="filter" />
|
|
119
|
+
{i18n("dataframe.filter")}
|
|
120
|
+
{#if filter_active}
|
|
121
|
+
<span class="priority">1</span>
|
|
122
|
+
{/if}
|
|
123
|
+
</button>
|
|
124
|
+
<button role="menuitem" on:click={on_clear_filter}>
|
|
125
|
+
<CellMenuIcons icon="clear-filter" />
|
|
126
|
+
{i18n("dataframe.clear_filter")}
|
|
127
|
+
</button>
|
|
88
128
|
{/if}
|
|
89
129
|
|
|
90
130
|
{#if !is_header && can_add_rows}
|
|
@@ -147,6 +187,10 @@
|
|
|
147
187
|
{/if}
|
|
148
188
|
</div>
|
|
149
189
|
|
|
190
|
+
{#if active_filter_menu}
|
|
191
|
+
<FilterMenu {on_filter} />
|
|
192
|
+
{/if}
|
|
193
|
+
|
|
150
194
|
<style>
|
|
151
195
|
.cell-menu {
|
|
152
196
|
position: fixed;
|
|
@@ -189,4 +189,52 @@
|
|
|
189
189
|
stroke-linecap="round"
|
|
190
190
|
/>
|
|
191
191
|
</svg>
|
|
192
|
+
{:else if icon == "filter"}
|
|
193
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
194
|
+
<path
|
|
195
|
+
d="M5 5H19"
|
|
196
|
+
stroke="currentColor"
|
|
197
|
+
stroke-width="2"
|
|
198
|
+
stroke-linecap="round"
|
|
199
|
+
/>
|
|
200
|
+
<path
|
|
201
|
+
d="M8 9H16"
|
|
202
|
+
stroke="currentColor"
|
|
203
|
+
stroke-width="2"
|
|
204
|
+
stroke-linecap="round"
|
|
205
|
+
/>
|
|
206
|
+
<path
|
|
207
|
+
d="M11 13H13"
|
|
208
|
+
stroke="currentColor"
|
|
209
|
+
stroke-width="2"
|
|
210
|
+
stroke-linecap="round"
|
|
211
|
+
/>
|
|
212
|
+
</svg>
|
|
213
|
+
{:else if icon == "clear-filter"}
|
|
214
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
215
|
+
<path
|
|
216
|
+
d="M5 5H19"
|
|
217
|
+
stroke="currentColor"
|
|
218
|
+
stroke-width="2"
|
|
219
|
+
stroke-linecap="round"
|
|
220
|
+
/>
|
|
221
|
+
<path
|
|
222
|
+
d="M8 9H16"
|
|
223
|
+
stroke="currentColor"
|
|
224
|
+
stroke-width="2"
|
|
225
|
+
stroke-linecap="round"
|
|
226
|
+
/>
|
|
227
|
+
<path
|
|
228
|
+
d="M11 13H13"
|
|
229
|
+
stroke="currentColor"
|
|
230
|
+
stroke-width="2"
|
|
231
|
+
stroke-linecap="round"
|
|
232
|
+
/>
|
|
233
|
+
<path
|
|
234
|
+
d="M17 17L21 21M21 17L17 21"
|
|
235
|
+
stroke="currentColor"
|
|
236
|
+
stroke-width="2"
|
|
237
|
+
stroke-linecap="round"
|
|
238
|
+
/>
|
|
239
|
+
</svg>
|
|
192
240
|
{/if}
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
export let line_breaks = true;
|
|
27
27
|
export let editable = true;
|
|
28
28
|
export let is_static = false;
|
|
29
|
-
export let root: string;
|
|
30
29
|
export let max_chars: number | null = null;
|
|
31
30
|
export let components: Record<string, any> = {};
|
|
32
31
|
export let i18n: I18nFormatter;
|
|
@@ -178,7 +177,6 @@
|
|
|
178
177
|
{latex_delimiters}
|
|
179
178
|
{line_breaks}
|
|
180
179
|
chatbot={false}
|
|
181
|
-
{root}
|
|
182
180
|
/>
|
|
183
181
|
{:else}
|
|
184
182
|
{display_text}
|
|
@@ -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,10 +855,10 @@
|
|
|
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}
|
|
808
|
-
{root}
|
|
809
862
|
{editable}
|
|
810
863
|
is_static={static_columns.includes(i)}
|
|
811
864
|
{i18n}
|
|
@@ -830,7 +883,6 @@
|
|
|
830
883
|
datatype={Array.isArray(datatype) ? datatype[j] : datatype}
|
|
831
884
|
edit={false}
|
|
832
885
|
el={null}
|
|
833
|
-
{root}
|
|
834
886
|
{editable}
|
|
835
887
|
{i18n}
|
|
836
888
|
show_selection_buttons={selected_cells.length === 1 &&
|
|
@@ -908,10 +960,10 @@
|
|
|
908
960
|
{toggle_header_menu}
|
|
909
961
|
{end_header_edit}
|
|
910
962
|
sort_columns={$df_state.sort_state.sort_columns}
|
|
963
|
+
filter_columns={$df_state.filter_state.filter_columns}
|
|
911
964
|
{latex_delimiters}
|
|
912
965
|
{line_breaks}
|
|
913
966
|
{max_chars}
|
|
914
|
-
{root}
|
|
915
967
|
{editable}
|
|
916
968
|
is_static={static_columns.includes(i)}
|
|
917
969
|
{i18n}
|
|
@@ -949,7 +1001,6 @@
|
|
|
949
1001
|
datatype={Array.isArray(datatype) ? datatype[j] : datatype}
|
|
950
1002
|
{editing}
|
|
951
1003
|
{max_chars}
|
|
952
|
-
{root}
|
|
953
1004
|
{editable}
|
|
954
1005
|
is_static={static_columns.includes(j)}
|
|
955
1006
|
{i18n}
|
|
@@ -1026,6 +1077,25 @@
|
|
|
1026
1077
|
(item) => item.col === (active_header_menu?.col ?? -1)
|
|
1027
1078
|
) + 1 || null
|
|
1028
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}
|
|
1029
1099
|
/>
|
|
1030
1100
|
{/if}
|
|
1031
1101
|
|
package/shared/TableCell.svelte
CHANGED
|
@@ -53,7 +53,6 @@
|
|
|
53
53
|
export let datatype: Datatype;
|
|
54
54
|
export let editing: [number, number] | false;
|
|
55
55
|
export let max_chars: number | undefined;
|
|
56
|
-
export let root: string;
|
|
57
56
|
export let editable: boolean;
|
|
58
57
|
export let is_static = false;
|
|
59
58
|
export let i18n: I18nFormatter;
|
|
@@ -139,7 +138,6 @@
|
|
|
139
138
|
}
|
|
140
139
|
}}
|
|
141
140
|
on:blur={handle_blur}
|
|
142
|
-
{root}
|
|
143
141
|
{max_chars}
|
|
144
142
|
{i18n}
|
|
145
143
|
{components}
|
|
@@ -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;
|
|
@@ -26,7 +34,6 @@
|
|
|
26
34
|
}[];
|
|
27
35
|
export let line_breaks: boolean;
|
|
28
36
|
export let max_chars: number | undefined;
|
|
29
|
-
export let root: string;
|
|
30
37
|
export let editable: boolean;
|
|
31
38
|
export let i18n: I18nFormatter;
|
|
32
39
|
export let el: HTMLInputElement | null;
|
|
@@ -35,6 +42,7 @@
|
|
|
35
42
|
|
|
36
43
|
$: can_add_columns = col_count && col_count[1] === "dynamic";
|
|
37
44
|
$: sort_index = sort_columns.findIndex((item) => item.col === i);
|
|
45
|
+
$: filter_index = filter_columns.findIndex((item) => item.col === i);
|
|
38
46
|
$: sort_priority = sort_index !== -1 ? sort_index + 1 : null;
|
|
39
47
|
$: current_direction =
|
|
40
48
|
sort_index !== -1 ? sort_columns[sort_index].direction : null;
|
|
@@ -64,6 +72,7 @@
|
|
|
64
72
|
class:last-pinned={i === actual_pinned_columns - 1}
|
|
65
73
|
class:focus={header_edit === i || selected_header === i}
|
|
66
74
|
class:sorted={sort_index !== -1}
|
|
75
|
+
class:filtered={filter_index !== -1}
|
|
67
76
|
aria-sort={get_sort_status(value, sort_columns, headers) === "none"
|
|
68
77
|
? "none"
|
|
69
78
|
: get_sort_status(value, sort_columns, headers) === "asc"
|
|
@@ -105,7 +114,6 @@
|
|
|
105
114
|
}
|
|
106
115
|
}}
|
|
107
116
|
header
|
|
108
|
-
{root}
|
|
109
117
|
{editable}
|
|
110
118
|
{is_static}
|
|
111
119
|
{i18n}
|
|
@@ -127,6 +135,13 @@
|
|
|
127
135
|
{/if}
|
|
128
136
|
</div>
|
|
129
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}
|
|
130
145
|
</button>
|
|
131
146
|
{#if is_static}
|
|
132
147
|
<Padlock />
|
|
@@ -245,6 +260,20 @@
|
|
|
245
260
|
padding: var(--size-1-5);
|
|
246
261
|
}
|
|
247
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
|
+
|
|
248
277
|
.pinned-column {
|
|
249
278
|
position: sticky;
|
|
250
279
|
z-index: 5;
|