@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
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}
|
|
@@ -36,15 +36,13 @@
|
|
|
36
36
|
export let coords: [number, number];
|
|
37
37
|
export let on_select_column: ((col: number) => void) | null = null;
|
|
38
38
|
export let on_select_row: ((row: number) => void) | null = null;
|
|
39
|
-
export let el:
|
|
39
|
+
export let el: HTMLTextAreaElement | null;
|
|
40
40
|
|
|
41
41
|
const dispatch = createEventDispatcher<{
|
|
42
42
|
blur: { blur_event: FocusEvent; coords: [number, number] };
|
|
43
43
|
keydown: KeyboardEvent;
|
|
44
44
|
}>();
|
|
45
45
|
|
|
46
|
-
let is_expanded = false;
|
|
47
|
-
|
|
48
46
|
function truncate_text(
|
|
49
47
|
text: string | number,
|
|
50
48
|
max_length: number | null = null,
|
|
@@ -57,8 +55,7 @@
|
|
|
57
55
|
return str.slice(0, max_length) + "...";
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
$: should_truncate =
|
|
61
|
-
!edit && !is_expanded && max_chars !== null && max_chars > 0;
|
|
58
|
+
$: should_truncate = !edit && max_chars !== null && max_chars > 0;
|
|
62
59
|
|
|
63
60
|
$: display_content = editable
|
|
64
61
|
? value
|
|
@@ -70,7 +67,7 @@
|
|
|
70
67
|
? truncate_text(display_content, max_chars, datatype === "image")
|
|
71
68
|
: display_content;
|
|
72
69
|
|
|
73
|
-
function use_focus(node:
|
|
70
|
+
function use_focus(node: HTMLTextAreaElement): any {
|
|
74
71
|
requestAnimationFrame(() => {
|
|
75
72
|
node.focus();
|
|
76
73
|
});
|
|
@@ -86,20 +83,9 @@
|
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
function handle_keydown(event: KeyboardEvent): void {
|
|
89
|
-
if (event.key === "Enter") {
|
|
90
|
-
if (!header) {
|
|
91
|
-
is_expanded = !is_expanded;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
86
|
dispatch("keydown", event);
|
|
95
87
|
}
|
|
96
88
|
|
|
97
|
-
function handle_click(): void {
|
|
98
|
-
if (!edit && !header) {
|
|
99
|
-
is_expanded = !is_expanded;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
89
|
function handle_bool_change(new_value: boolean): void {
|
|
104
90
|
value = new_value.toString();
|
|
105
91
|
dispatch("blur", {
|
|
@@ -116,10 +102,9 @@
|
|
|
116
102
|
</script>
|
|
117
103
|
|
|
118
104
|
{#if edit && datatype !== "bool"}
|
|
119
|
-
<
|
|
105
|
+
<textarea
|
|
120
106
|
readonly={is_static}
|
|
121
107
|
aria-readonly={is_static}
|
|
122
|
-
role="textbox"
|
|
123
108
|
aria-label={is_static ? "Cell is read-only" : "Edit cell"}
|
|
124
109
|
bind:this={el}
|
|
125
110
|
bind:value
|
|
@@ -127,7 +112,6 @@
|
|
|
127
112
|
tabindex="-1"
|
|
128
113
|
on:blur={handle_blur}
|
|
129
114
|
on:mousedown|stopPropagation
|
|
130
|
-
on:mouseup|stopPropagation
|
|
131
115
|
on:click|stopPropagation
|
|
132
116
|
use:use_focus
|
|
133
117
|
on:keydown={handle_keydown}
|
|
@@ -143,18 +127,17 @@
|
|
|
143
127
|
{:else}
|
|
144
128
|
<span
|
|
145
129
|
class:dragging={is_dragging}
|
|
146
|
-
on:click={handle_click}
|
|
147
130
|
on:keydown={handle_keydown}
|
|
148
131
|
tabindex="0"
|
|
149
132
|
role="button"
|
|
150
133
|
class:edit
|
|
151
|
-
class:expanded={
|
|
134
|
+
class:expanded={edit}
|
|
152
135
|
class:multiline={header}
|
|
153
136
|
on:focus|preventDefault
|
|
154
137
|
style={styling}
|
|
155
138
|
data-editable={editable}
|
|
156
139
|
data-max-chars={max_chars}
|
|
157
|
-
data-expanded={
|
|
140
|
+
data-expanded={edit}
|
|
158
141
|
placeholder=" "
|
|
159
142
|
class:text={datatype === "str"}
|
|
160
143
|
class:wrap={wrap_text}
|
|
@@ -202,7 +185,7 @@
|
|
|
202
185
|
cursor: crosshair !important;
|
|
203
186
|
}
|
|
204
187
|
|
|
205
|
-
|
|
188
|
+
textarea {
|
|
206
189
|
position: absolute;
|
|
207
190
|
flex: 1 1 0%;
|
|
208
191
|
transform: translateX(-0.1px);
|
|
@@ -211,6 +194,16 @@
|
|
|
211
194
|
background: transparent;
|
|
212
195
|
cursor: text;
|
|
213
196
|
width: calc(100% - var(--size-2));
|
|
197
|
+
resize: none;
|
|
198
|
+
height: 100%;
|
|
199
|
+
padding-left: 0;
|
|
200
|
+
font-size: inherit;
|
|
201
|
+
font-weight: inherit;
|
|
202
|
+
line-height: var(--line-lg);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
textarea:focus {
|
|
206
|
+
outline: none;
|
|
214
207
|
}
|
|
215
208
|
|
|
216
209
|
span {
|
|
@@ -262,7 +255,7 @@
|
|
|
262
255
|
object-fit: contain;
|
|
263
256
|
}
|
|
264
257
|
|
|
265
|
-
|
|
258
|
+
textarea:read-only {
|
|
266
259
|
cursor: not-allowed;
|
|
267
260
|
}
|
|
268
261
|
|
|
@@ -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;
|
|
@@ -163,7 +165,7 @@
|
|
|
163
165
|
|
|
164
166
|
let els: Record<
|
|
165
167
|
string,
|
|
166
|
-
{ cell: null | HTMLTableCellElement; input: null |
|
|
168
|
+
{ cell: null | HTMLTableCellElement; input: null | HTMLTextAreaElement }
|
|
167
169
|
> = {};
|
|
168
170
|
let data_binding: Record<string, (typeof data)[0][0]> = {};
|
|
169
171
|
let _headers = make_headers(headers, col_count, els, make_id);
|
|
@@ -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
|
|
package/shared/TableCell.svelte
CHANGED
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
export let components: Record<string, any> = {};
|
|
60
60
|
export let el: {
|
|
61
61
|
cell: HTMLTableCellElement | null;
|
|
62
|
-
input:
|
|
62
|
+
input: HTMLTextAreaElement | null;
|
|
63
63
|
};
|
|
64
64
|
export let handle_select_column: (col: number) => void;
|
|
65
65
|
export let handle_select_row: (row: number) => void;
|