@gradio/dataframe 0.22.0 → 0.23.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 +24 -0
- package/Dataframe.test.ts +777 -31
- package/Index.svelte +107 -106
- package/dist/Index.svelte +107 -106
- package/dist/Index.svelte.d.ts +0 -1
- package/dist/shared/BooleanCell.svelte +12 -6
- package/dist/shared/BooleanCell.svelte.d.ts +4 -18
- package/dist/shared/CellMenu.svelte +74 -49
- package/dist/shared/CellMenu.svelte.d.ts +5 -19
- package/dist/shared/CellMenuButton.svelte +3 -3
- package/dist/shared/CellMenuButton.svelte.d.ts +4 -18
- package/dist/shared/CellMenuIcons.svelte +1 -1
- package/dist/shared/CellMenuIcons.svelte.d.ts +4 -18
- package/dist/shared/DataCell.svelte +189 -0
- package/dist/shared/DataCell.svelte.d.ts +44 -0
- package/dist/shared/EditableCell.svelte +88 -64
- package/dist/shared/EditableCell.svelte.d.ts +8 -26
- package/dist/shared/EmptyRowButton.svelte +2 -2
- package/dist/shared/EmptyRowButton.svelte.d.ts +4 -18
- package/dist/shared/FilterMenu.svelte +26 -18
- package/dist/shared/FilterMenu.svelte.d.ts +5 -21
- package/dist/shared/HeaderCell.svelte +218 -0
- package/dist/shared/HeaderCell.svelte.d.ts +30 -0
- package/dist/shared/RowNumber.svelte +4 -2
- package/dist/shared/RowNumber.svelte.d.ts +4 -18
- package/dist/shared/Table.svelte +1037 -949
- package/dist/shared/Table.svelte.d.ts +12 -30
- package/dist/shared/Toolbar.svelte +32 -22
- package/dist/shared/Toolbar.svelte.d.ts +6 -21
- package/dist/shared/column_measurement.svelte.d.ts +20 -0
- package/dist/shared/column_measurement.svelte.js +79 -0
- package/dist/shared/icons/FilterIcon.svelte +14 -6
- package/dist/shared/icons/FilterIcon.svelte.d.ts +14 -6
- package/dist/shared/icons/Padlock.svelte +6 -2
- package/dist/shared/icons/Padlock.svelte.d.ts +4 -25
- package/dist/shared/icons/SelectionButtons.svelte +23 -8
- package/dist/shared/icons/SelectionButtons.svelte.d.ts +4 -18
- package/dist/shared/icons/SortButtonDown.svelte +15 -4
- package/dist/shared/icons/SortButtonDown.svelte.d.ts +4 -25
- package/dist/shared/icons/SortButtonUp.svelte +15 -5
- package/dist/shared/icons/SortButtonUp.svelte.d.ts +4 -25
- package/dist/shared/icons/SortIcon.svelte +13 -7
- package/dist/shared/icons/SortIcon.svelte.d.ts +7 -21
- package/dist/shared/tanstack/index.d.ts +3 -0
- package/dist/shared/tanstack/index.js +3 -0
- package/dist/shared/tanstack/table.svelte.d.ts +26 -0
- package/dist/shared/tanstack/table.svelte.js +102 -0
- package/dist/shared/tanstack/virtual.svelte.d.ts +14 -0
- package/dist/shared/tanstack/virtual.svelte.js +76 -0
- package/dist/shared/types.d.ts +2 -0
- package/dist/shared/utils/filter.d.ts +5 -0
- package/dist/shared/utils/filter.js +62 -0
- package/dist/shared/utils/selection_utils.js +1 -1
- package/dist/shared/utils/table_utils.d.ts +1 -14
- package/dist/shared/utils/table_utils.js +0 -61
- package/dist/standalone/Index.svelte +71 -46
- package/dist/standalone/Index.svelte.d.ts +4 -23
- package/dist/types.d.ts +32 -0
- package/dist/types.js +1 -0
- package/package.json +12 -11
- package/shared/BooleanCell.svelte +12 -6
- package/shared/CellMenu.svelte +74 -49
- package/shared/CellMenuButton.svelte +3 -3
- package/shared/CellMenuIcons.svelte +1 -1
- package/shared/DataCell.svelte +189 -0
- package/shared/EditableCell.svelte +88 -64
- package/shared/EmptyRowButton.svelte +2 -2
- package/shared/FilterMenu.svelte +26 -18
- package/shared/HeaderCell.svelte +218 -0
- package/shared/RowNumber.svelte +4 -2
- package/shared/Table.svelte +1037 -949
- package/shared/Toolbar.svelte +32 -22
- package/shared/column_measurement.svelte.ts +103 -0
- package/shared/icons/FilterIcon.svelte +14 -6
- package/shared/icons/Padlock.svelte +6 -2
- package/shared/icons/SelectionButtons.svelte +23 -8
- package/shared/icons/SortButtonDown.svelte +15 -4
- package/shared/icons/SortButtonUp.svelte +15 -5
- package/shared/icons/SortIcon.svelte +13 -7
- package/shared/tanstack/index.ts +23 -0
- package/shared/tanstack/table.svelte.ts +117 -0
- package/shared/tanstack/virtual.svelte.ts +105 -0
- package/shared/types.ts +3 -0
- package/shared/utils/filter.ts +66 -0
- package/shared/utils/selection_utils.ts +1 -1
- package/shared/utils/table_utils.ts +1 -100
- package/standalone/Index.svelte +71 -46
- package/test/filter.test.ts +350 -0
- package/test/selection_utils.test.ts +416 -0
- package/test/table_utils.test.ts +45 -112
- package/types.ts +34 -0
- package/dist/shared/TableCell.svelte +0 -330
- package/dist/shared/TableCell.svelte.d.ts +0 -66
- package/dist/shared/TableHeader.svelte +0 -344
- package/dist/shared/TableHeader.svelte.d.ts +0 -61
- package/dist/shared/VirtualTable.svelte +0 -379
- package/dist/shared/VirtualTable.svelte.d.ts +0 -42
- package/dist/shared/context/dataframe_context.d.ts +0 -180
- package/dist/shared/context/dataframe_context.js +0 -412
- package/dist/shared/icons/SortArrowDown.svelte +0 -25
- package/dist/shared/icons/SortArrowDown.svelte.d.ts +0 -20
- package/dist/shared/icons/SortArrowUp.svelte +0 -25
- package/dist/shared/icons/SortArrowUp.svelte.d.ts +0 -20
- package/dist/shared/utils/data_processing.d.ts +0 -14
- package/dist/shared/utils/data_processing.js +0 -47
- package/dist/shared/utils/drag_utils.d.ts +0 -15
- package/dist/shared/utils/drag_utils.js +0 -67
- package/dist/shared/utils/filter_utils.d.ts +0 -29
- package/dist/shared/utils/filter_utils.js +0 -123
- package/dist/shared/utils/keyboard_utils.d.ts +0 -3
- package/dist/shared/utils/keyboard_utils.js +0 -236
- package/dist/shared/utils/menu_utils.d.ts +0 -42
- package/dist/shared/utils/menu_utils.js +0 -58
- package/dist/shared/utils/sort_utils.d.ts +0 -26
- package/dist/shared/utils/sort_utils.js +0 -59
- package/shared/TableCell.svelte +0 -330
- package/shared/TableHeader.svelte +0 -344
- package/shared/VirtualTable.svelte +0 -379
- package/shared/context/dataframe_context.ts +0 -701
- package/shared/icons/SortArrowDown.svelte +0 -25
- package/shared/icons/SortArrowUp.svelte +0 -25
- package/shared/utils/data_processing.ts +0 -77
- package/shared/utils/drag_utils.ts +0 -105
- package/shared/utils/filter_utils.ts +0 -208
- package/shared/utils/keyboard_utils.ts +0 -309
- package/shared/utils/menu_utils.ts +0 -115
- package/shared/utils/sort_utils.ts +0 -91
- package/test/sort_utils.test.ts +0 -323
package/shared/Toolbar.svelte
CHANGED
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Copy, Check } from "@gradio/icons";
|
|
3
3
|
import { FullscreenButton } from "@gradio/atoms";
|
|
4
|
-
import { onDestroy } from "svelte";
|
|
5
|
-
import { createEventDispatcher } from "svelte";
|
|
6
4
|
import { IconButton } from "@gradio/atoms";
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
let {
|
|
7
|
+
show_fullscreen_button = false,
|
|
8
|
+
show_copy_button = false,
|
|
9
|
+
show_search = "none",
|
|
10
|
+
fullscreen = false,
|
|
11
|
+
on_copy,
|
|
12
|
+
on_commit_filter,
|
|
13
|
+
current_search_query = null,
|
|
14
|
+
onsearch,
|
|
15
|
+
onfullscreen
|
|
16
|
+
}: {
|
|
17
|
+
show_fullscreen_button?: boolean;
|
|
18
|
+
show_copy_button?: boolean;
|
|
19
|
+
show_search?: "none" | "search" | "filter";
|
|
20
|
+
fullscreen?: boolean;
|
|
21
|
+
on_copy: () => Promise<void>;
|
|
22
|
+
on_commit_filter: () => void;
|
|
23
|
+
current_search_query?: string | null;
|
|
24
|
+
onsearch?: (query: string | null) => void;
|
|
25
|
+
onfullscreen?: () => void;
|
|
26
|
+
} = $props();
|
|
27
|
+
|
|
28
|
+
let copied = $state(false);
|
|
20
29
|
let timer: ReturnType<typeof setTimeout>;
|
|
21
|
-
|
|
22
|
-
let input_value = "";
|
|
30
|
+
let input_value = $state("");
|
|
23
31
|
|
|
24
32
|
function handle_search_input(e: Event): void {
|
|
25
33
|
const target = e.target as HTMLTextAreaElement;
|
|
@@ -27,7 +35,7 @@
|
|
|
27
35
|
const new_query = input_value || null;
|
|
28
36
|
if (current_search_query !== new_query) {
|
|
29
37
|
current_search_query = new_query;
|
|
30
|
-
|
|
38
|
+
onsearch?.(current_search_query);
|
|
31
39
|
}
|
|
32
40
|
}
|
|
33
41
|
|
|
@@ -44,8 +52,10 @@
|
|
|
44
52
|
copy_feedback();
|
|
45
53
|
}
|
|
46
54
|
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
$effect(() => {
|
|
56
|
+
return () => {
|
|
57
|
+
if (timer) clearTimeout(timer);
|
|
58
|
+
};
|
|
49
59
|
});
|
|
50
60
|
</script>
|
|
51
61
|
|
|
@@ -56,7 +66,7 @@
|
|
|
56
66
|
<input
|
|
57
67
|
type="text"
|
|
58
68
|
value={current_search_query || ""}
|
|
59
|
-
|
|
69
|
+
oninput={handle_search_input}
|
|
60
70
|
placeholder={show_search === "filter" ? "Filter..." : "Search..."}
|
|
61
71
|
class="search-input"
|
|
62
72
|
class:filter-mode={show_search === "filter"}
|
|
@@ -65,7 +75,7 @@
|
|
|
65
75
|
{#if current_search_query && show_search === "filter"}
|
|
66
76
|
<button
|
|
67
77
|
class="toolbar-button check-button"
|
|
68
|
-
|
|
78
|
+
onclick={on_commit_filter}
|
|
69
79
|
aria-label="Apply filter and update dataframe values"
|
|
70
80
|
title="Apply filter and update dataframe values"
|
|
71
81
|
>
|
|
@@ -82,7 +92,7 @@
|
|
|
82
92
|
/>
|
|
83
93
|
{/if}
|
|
84
94
|
{#if show_fullscreen_button}
|
|
85
|
-
<FullscreenButton {fullscreen}
|
|
95
|
+
<FullscreenButton {fullscreen} onclick={(_fs) => onfullscreen?.()} />
|
|
86
96
|
{/if}
|
|
87
97
|
</div>
|
|
88
98
|
</div>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* measures header cell positions/widths and provides inline styles
|
|
3
|
+
* for absolutely-positioned body cells to match.
|
|
4
|
+
*/
|
|
5
|
+
export function create_column_measurement(opts: {
|
|
6
|
+
header_row_el: () => HTMLTableRowElement | undefined;
|
|
7
|
+
header_table_el: () => HTMLTableElement | undefined;
|
|
8
|
+
resolved_headers: () => string[];
|
|
9
|
+
row_data: () => any[];
|
|
10
|
+
show_row_numbers: () => boolean;
|
|
11
|
+
column_widths: () => string[];
|
|
12
|
+
on_resize?: () => void;
|
|
13
|
+
}): {
|
|
14
|
+
readonly col_widths: number[];
|
|
15
|
+
readonly col_lefts: number[];
|
|
16
|
+
readonly total_header_width: number;
|
|
17
|
+
readonly header_height: number;
|
|
18
|
+
readonly row_num_width: number;
|
|
19
|
+
get_col_style: (index: number) => string;
|
|
20
|
+
} {
|
|
21
|
+
let col_widths: number[] = $state([]);
|
|
22
|
+
let total_header_width: number = $state(0);
|
|
23
|
+
let header_height: number = $state(0);
|
|
24
|
+
|
|
25
|
+
// use offsets (not just widths) avoids accumulated subpixel rounding errors
|
|
26
|
+
let col_lefts: number[] = $state([]);
|
|
27
|
+
|
|
28
|
+
function measure(): void {
|
|
29
|
+
const current_row_el = opts.header_row_el();
|
|
30
|
+
const current_table_el = opts.header_table_el();
|
|
31
|
+
if (!current_row_el) return;
|
|
32
|
+
|
|
33
|
+
const cells = current_row_el.querySelectorAll<HTMLElement>(".header-cell");
|
|
34
|
+
const table_rect = current_table_el?.getBoundingClientRect();
|
|
35
|
+
const table_left = table_rect?.left ?? 0;
|
|
36
|
+
|
|
37
|
+
col_lefts = Array.from(cells).map(
|
|
38
|
+
(c) => c.getBoundingClientRect().left - table_left
|
|
39
|
+
);
|
|
40
|
+
col_widths = Array.from(cells).map((c) => c.getBoundingClientRect().width);
|
|
41
|
+
if (current_table_el) {
|
|
42
|
+
total_header_width = table_rect?.width ?? 0;
|
|
43
|
+
header_height = table_rect?.height ?? 0;
|
|
44
|
+
}
|
|
45
|
+
opts.on_resize?.();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
$effect(() => {
|
|
49
|
+
const table_el = opts.header_table_el();
|
|
50
|
+
const row_el = opts.header_row_el();
|
|
51
|
+
if (!table_el || !row_el) return;
|
|
52
|
+
|
|
53
|
+
// re-run when columns change so we observe new cells
|
|
54
|
+
opts.resolved_headers();
|
|
55
|
+
|
|
56
|
+
const ro = new ResizeObserver(measure);
|
|
57
|
+
ro.observe(table_el);
|
|
58
|
+
|
|
59
|
+
// observe individual header cells so content-driven column
|
|
60
|
+
// width changes (editing, data updates) trigger a re-measure
|
|
61
|
+
// even when the overall table dimensions stay the same
|
|
62
|
+
const cells = row_el.querySelectorAll<HTMLElement>(".header-cell");
|
|
63
|
+
cells.forEach((cell) => ro.observe(cell));
|
|
64
|
+
|
|
65
|
+
return () => ro.disconnect();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
let row_num_width = $derived.by(() => {
|
|
69
|
+
if (!opts.show_row_numbers()) return 0;
|
|
70
|
+
const row_el = opts.header_row_el();
|
|
71
|
+
if (!row_el) return 0;
|
|
72
|
+
const el = row_el.querySelector<HTMLElement>(".row-number-header");
|
|
73
|
+
return el?.getBoundingClientRect().width ?? 48;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
function get_col_style(index: number): string {
|
|
77
|
+
if (col_widths[index] !== undefined) {
|
|
78
|
+
return `width: ${col_widths[index]}px; flex: 0 0 ${col_widths[index]}px;`;
|
|
79
|
+
}
|
|
80
|
+
const user_widths = opts.column_widths();
|
|
81
|
+
if (user_widths[index]) return `width: ${user_widths[index]};`;
|
|
82
|
+
return "width: 150px;";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
get col_widths() {
|
|
87
|
+
return col_widths;
|
|
88
|
+
},
|
|
89
|
+
get col_lefts() {
|
|
90
|
+
return col_lefts;
|
|
91
|
+
},
|
|
92
|
+
get total_header_width() {
|
|
93
|
+
return total_header_width;
|
|
94
|
+
},
|
|
95
|
+
get header_height() {
|
|
96
|
+
return header_height;
|
|
97
|
+
},
|
|
98
|
+
get row_num_width() {
|
|
99
|
+
return row_num_width;
|
|
100
|
+
},
|
|
101
|
+
get_col_style
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
<
|
|
2
|
-
</script>
|
|
3
|
-
|
|
4
|
-
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
1
|
+
<svg viewBox="0 0 24 24" width="20" height="20">
|
|
5
2
|
<path
|
|
6
|
-
d="
|
|
3
|
+
d="M5 5H19"
|
|
4
|
+
stroke="currentColor"
|
|
5
|
+
stroke-width="2"
|
|
6
|
+
stroke-linecap="round"
|
|
7
|
+
/>
|
|
8
|
+
<path
|
|
9
|
+
d="M8 9H16"
|
|
10
|
+
stroke="currentColor"
|
|
11
|
+
stroke-width="2"
|
|
12
|
+
stroke-linecap="round"
|
|
13
|
+
/>
|
|
14
|
+
<path
|
|
15
|
+
d="M11 13H13"
|
|
7
16
|
stroke="currentColor"
|
|
8
17
|
stroke-width="2"
|
|
9
18
|
stroke-linecap="round"
|
|
10
|
-
stroke-linejoin="round"
|
|
11
19
|
/>
|
|
12
20
|
</svg>
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let { size = 16 } = $props();
|
|
3
|
+
</script>
|
|
4
|
+
|
|
1
5
|
<div class="wrapper" aria-label="Static column">
|
|
2
6
|
<svg
|
|
3
7
|
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
-
width=
|
|
5
|
-
height=
|
|
8
|
+
width={size}
|
|
9
|
+
height={size}
|
|
6
10
|
viewBox="0 0 24 24"
|
|
7
11
|
fill="none"
|
|
8
12
|
stroke="currentColor"
|
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
let {
|
|
3
|
+
position,
|
|
4
|
+
coords,
|
|
5
|
+
on_click = null
|
|
6
|
+
}: {
|
|
7
|
+
position: "column" | "row";
|
|
8
|
+
coords: [number, number];
|
|
9
|
+
on_click?: (() => void) | null;
|
|
10
|
+
} = $props();
|
|
5
11
|
|
|
6
|
-
|
|
7
|
-
position === "column" ? coords[0] === 0 : coords[1] === 0
|
|
8
|
-
|
|
12
|
+
let is_first_position = $derived(
|
|
13
|
+
position === "column" ? coords[0] === 0 : coords[1] === 0
|
|
14
|
+
);
|
|
15
|
+
let direction = $derived(
|
|
9
16
|
position === "column"
|
|
10
17
|
? is_first_position
|
|
11
18
|
? "down"
|
|
12
19
|
: "up"
|
|
13
20
|
: is_first_position
|
|
14
21
|
? "right"
|
|
15
|
-
: "left"
|
|
22
|
+
: "left"
|
|
23
|
+
);
|
|
16
24
|
</script>
|
|
17
25
|
|
|
18
26
|
<button
|
|
19
27
|
class="selection-button selection-button-{position} {is_first_position
|
|
20
28
|
? `move-${direction}`
|
|
21
29
|
: ''}"
|
|
22
|
-
|
|
30
|
+
onclick={(e: MouseEvent) => {
|
|
31
|
+
e.stopPropagation();
|
|
32
|
+
on_click && on_click();
|
|
33
|
+
}}
|
|
23
34
|
aria-label={`Select ${position}`}
|
|
24
35
|
>
|
|
25
36
|
<span class={direction}>
|
|
@@ -50,6 +61,8 @@
|
|
|
50
61
|
|
|
51
62
|
.selection-button-row {
|
|
52
63
|
left: calc(var(--size-2-5) * -1);
|
|
64
|
+
top: 50%;
|
|
65
|
+
transform: translateY(-50%);
|
|
53
66
|
border-radius: var(--radius-sm) 0 0 var(--radius-sm);
|
|
54
67
|
}
|
|
55
68
|
|
|
@@ -62,6 +75,8 @@
|
|
|
62
75
|
.move-right {
|
|
63
76
|
left: auto;
|
|
64
77
|
right: calc(var(--size-2-5) * -1);
|
|
78
|
+
top: 50%;
|
|
79
|
+
transform: translateY(-50%);
|
|
65
80
|
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
|
|
66
81
|
}
|
|
67
82
|
|
|
@@ -1,14 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let { size = 16 } = $props();
|
|
3
|
+
</script>
|
|
4
|
+
|
|
1
5
|
<svg
|
|
2
|
-
|
|
6
|
+
width={size}
|
|
7
|
+
height={size}
|
|
8
|
+
viewBox="0 0 16 16"
|
|
3
9
|
fill="none"
|
|
4
10
|
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
-
focusable="false"
|
|
6
11
|
>
|
|
7
12
|
<path
|
|
8
|
-
d="
|
|
13
|
+
d="M4 8L8 12L12 8"
|
|
9
14
|
stroke="currentColor"
|
|
10
|
-
stroke-width="
|
|
15
|
+
stroke-width="1.5"
|
|
11
16
|
stroke-linecap="round"
|
|
12
17
|
stroke-linejoin="round"
|
|
13
18
|
/>
|
|
19
|
+
<path
|
|
20
|
+
d="M8 12V4"
|
|
21
|
+
stroke="currentColor"
|
|
22
|
+
stroke-width="1.5"
|
|
23
|
+
stroke-linecap="round"
|
|
24
|
+
/>
|
|
14
25
|
</svg>
|
|
@@ -1,15 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let { size = 16 } = $props();
|
|
3
|
+
</script>
|
|
4
|
+
|
|
1
5
|
<svg
|
|
2
|
-
|
|
6
|
+
width={size}
|
|
7
|
+
height={size}
|
|
8
|
+
viewBox="0 0 16 16"
|
|
3
9
|
fill="none"
|
|
4
10
|
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
-
aria-hidden="true"
|
|
6
|
-
focusable="false"
|
|
7
11
|
>
|
|
8
12
|
<path
|
|
9
|
-
d="
|
|
13
|
+
d="M4 8L8 4L12 8"
|
|
10
14
|
stroke="currentColor"
|
|
11
|
-
stroke-width="
|
|
15
|
+
stroke-width="1.5"
|
|
12
16
|
stroke-linecap="round"
|
|
13
17
|
stroke-linejoin="round"
|
|
14
18
|
/>
|
|
19
|
+
<path
|
|
20
|
+
d="M8 4V12"
|
|
21
|
+
stroke="currentColor"
|
|
22
|
+
stroke-width="1.5"
|
|
23
|
+
stroke-linecap="round"
|
|
24
|
+
/>
|
|
15
25
|
</svg>
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { createEventDispatcher } from "svelte";
|
|
3
2
|
import type { I18nFormatter } from "@gradio/utils";
|
|
4
3
|
import SortButtonUp from "./SortButtonUp.svelte";
|
|
5
4
|
import SortButtonDown from "./SortButtonDown.svelte";
|
|
6
5
|
import { IconButton } from "@gradio/atoms";
|
|
7
6
|
type SortDirection = "asc" | "desc";
|
|
8
|
-
export let direction: SortDirection | null = null;
|
|
9
|
-
export let priority: number | null = null;
|
|
10
|
-
export let i18n: I18nFormatter;
|
|
11
7
|
|
|
12
|
-
|
|
8
|
+
let {
|
|
9
|
+
direction = null,
|
|
10
|
+
priority = null,
|
|
11
|
+
i18n,
|
|
12
|
+
onsort
|
|
13
|
+
}: {
|
|
14
|
+
direction?: SortDirection | null;
|
|
15
|
+
priority?: number | null;
|
|
16
|
+
i18n: I18nFormatter;
|
|
17
|
+
onsort?: (direction: SortDirection) => void;
|
|
18
|
+
} = $props();
|
|
13
19
|
</script>
|
|
14
20
|
|
|
15
21
|
<div class="sort-icons" role="group" aria-label={i18n("dataframe.sort_column")}>
|
|
@@ -25,7 +31,7 @@
|
|
|
25
31
|
highlight={direction === "asc"}
|
|
26
32
|
onclick={(event) => {
|
|
27
33
|
event.stopPropagation();
|
|
28
|
-
|
|
34
|
+
onsort?.("asc");
|
|
29
35
|
}}
|
|
30
36
|
></IconButton>
|
|
31
37
|
<IconButton
|
|
@@ -35,7 +41,7 @@
|
|
|
35
41
|
highlight={direction === "desc"}
|
|
36
42
|
onclick={(event) => {
|
|
37
43
|
event.stopPropagation();
|
|
38
|
-
|
|
44
|
+
onsort?.("desc");
|
|
39
45
|
}}
|
|
40
46
|
></IconButton>
|
|
41
47
|
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export { createSvelteTable } from "./table.svelte.js";
|
|
2
|
+
|
|
3
|
+
export { createSvelteVirtualizer, type VirtualItem } from "./virtual.svelte.js";
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
createColumnHelper,
|
|
7
|
+
getCoreRowModel,
|
|
8
|
+
getSortedRowModel,
|
|
9
|
+
getFilteredRowModel,
|
|
10
|
+
type ColumnDef,
|
|
11
|
+
type TableOptions,
|
|
12
|
+
type Table,
|
|
13
|
+
type Header,
|
|
14
|
+
type Cell,
|
|
15
|
+
type Row,
|
|
16
|
+
type SortingState,
|
|
17
|
+
type ColumnFiltersState,
|
|
18
|
+
type ColumnPinningState,
|
|
19
|
+
type FilterFn,
|
|
20
|
+
type SortingFn,
|
|
21
|
+
type CellContext,
|
|
22
|
+
type HeaderContext
|
|
23
|
+
} from "@tanstack/table-core";
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTable,
|
|
3
|
+
type RowData,
|
|
4
|
+
type TableOptions,
|
|
5
|
+
type TableOptionsResolved,
|
|
6
|
+
type TableState
|
|
7
|
+
} from "@tanstack/table-core";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Merges objects while preserving property getters for lazy evaluation.
|
|
11
|
+
* Properties are defined as getters that look up values from sources in
|
|
12
|
+
* reverse order at access time. This is critical: it means reading a
|
|
13
|
+
* property from the merged result doesn't happen until the property is
|
|
14
|
+
* actually accessed, not when mergeObjects is called.
|
|
15
|
+
*/
|
|
16
|
+
export function mergeObjects(...sources: any): any {
|
|
17
|
+
const target: Record<string, any> = {};
|
|
18
|
+
for (let i = 0; i < sources.length; i++) {
|
|
19
|
+
let source = sources[i];
|
|
20
|
+
if (typeof source === "function") source = source();
|
|
21
|
+
if (source) {
|
|
22
|
+
const descriptors = Object.getOwnPropertyDescriptors(source);
|
|
23
|
+
for (const key in descriptors) {
|
|
24
|
+
if (key in target) continue;
|
|
25
|
+
Object.defineProperty(target, key, {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get() {
|
|
28
|
+
for (let j = sources.length - 1; j >= 0; j--) {
|
|
29
|
+
let s = sources[j];
|
|
30
|
+
if (typeof s === "function") s = s();
|
|
31
|
+
const v = (s || {})[key];
|
|
32
|
+
if (v !== undefined) return v;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return target;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Creates a reactive TanStack Table for Svelte 5.
|
|
44
|
+
*
|
|
45
|
+
* The reactivity works through mergeObjects' lazy getters:
|
|
46
|
+
* - $effect.pre calls table.setOptions() with a merged object
|
|
47
|
+
* - The merged object has lazy getters for `data`, `columns`, `state`, etc.
|
|
48
|
+
* - TanStack stores this object but doesn't deep-read all properties immediately
|
|
49
|
+
* - When getRowModel() is called (in $derived), TanStack reads `data` and `columns`
|
|
50
|
+
* through the lazy getters, which read the reactive $state/$derived values
|
|
51
|
+
* - onStateChange fires when TanStack mutates its own state → bumps version
|
|
52
|
+
* - version is read by getRowModel/getHeaderGroups → $derived re-evaluates
|
|
53
|
+
*/
|
|
54
|
+
export function createSvelteTable<TData extends RowData>(
|
|
55
|
+
options: TableOptions<TData>
|
|
56
|
+
) {
|
|
57
|
+
const resolvedOptions: TableOptionsResolved<TData> = mergeObjects(
|
|
58
|
+
{
|
|
59
|
+
state: {},
|
|
60
|
+
onStateChange() {},
|
|
61
|
+
renderFallbackValue: null,
|
|
62
|
+
mergeOptions: (
|
|
63
|
+
defaultOptions: TableOptions<TData>,
|
|
64
|
+
opts: Partial<TableOptions<TData>>
|
|
65
|
+
) => {
|
|
66
|
+
return mergeObjects(defaultOptions, opts);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
options
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const table = createTable(resolvedOptions);
|
|
73
|
+
let state = $state<Partial<TableState>>(table.initialState);
|
|
74
|
+
let version = $state(0);
|
|
75
|
+
|
|
76
|
+
function updateOptions(): void {
|
|
77
|
+
table.setOptions((prev) => {
|
|
78
|
+
// mergeObjects creates lazy getters — `state` is NOT read here,
|
|
79
|
+
// only when TanStack accesses the `state` property later.
|
|
80
|
+
return mergeObjects(prev, options, {
|
|
81
|
+
state: mergeObjects(state, options.state || {}),
|
|
82
|
+
onStateChange: (updater: any) => {
|
|
83
|
+
if (updater instanceof Function) state = updater(state);
|
|
84
|
+
else state = mergeObjects(state, updater);
|
|
85
|
+
version += 1;
|
|
86
|
+
options.onStateChange?.(updater);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Initial sync
|
|
93
|
+
updateOptions();
|
|
94
|
+
|
|
95
|
+
// Re-sync when options change. Because mergeObjects uses lazy getters,
|
|
96
|
+
// this effect's tracked dependencies are only the properties that
|
|
97
|
+
// table.setOptions() eagerly reads (which is minimal — mostly just
|
|
98
|
+
// checking if the options object reference changed).
|
|
99
|
+
$effect.pre(() => {
|
|
100
|
+
updateOptions();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
getRowModel: () => {
|
|
105
|
+
void version;
|
|
106
|
+
return table.getRowModel();
|
|
107
|
+
},
|
|
108
|
+
getHeaderGroups: () => {
|
|
109
|
+
void version;
|
|
110
|
+
return table.getHeaderGroups();
|
|
111
|
+
},
|
|
112
|
+
getColumn: (id: string) => {
|
|
113
|
+
void version;
|
|
114
|
+
return table.getColumn(id);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Virtualizer,
|
|
3
|
+
elementScroll,
|
|
4
|
+
observeElementOffset,
|
|
5
|
+
observeElementRect,
|
|
6
|
+
type PartialKeys,
|
|
7
|
+
type VirtualizerOptions
|
|
8
|
+
} from "@tanstack/virtual-core";
|
|
9
|
+
import { untrack } from "svelte";
|
|
10
|
+
|
|
11
|
+
export type { VirtualItem } from "@tanstack/virtual-core";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates a reactive TanStack Virtualizer for Svelte 5.
|
|
15
|
+
*
|
|
16
|
+
* Returns a getter function that returns the virtualizer instance.
|
|
17
|
+
* Call the getter inside $derived or template expressions to create
|
|
18
|
+
* reactive dependencies on the virtualizer state.
|
|
19
|
+
*/
|
|
20
|
+
export function createSvelteVirtualizer<
|
|
21
|
+
TScrollElement extends Element,
|
|
22
|
+
TItemElement extends Element
|
|
23
|
+
>(
|
|
24
|
+
options: PartialKeys<
|
|
25
|
+
VirtualizerOptions<TScrollElement, TItemElement>,
|
|
26
|
+
"observeElementRect" | "observeElementOffset" | "scrollToFn"
|
|
27
|
+
>
|
|
28
|
+
): {
|
|
29
|
+
instance: Virtualizer<TScrollElement, TItemElement>;
|
|
30
|
+
virtualItems: () => ReturnType<
|
|
31
|
+
Virtualizer<TScrollElement, TItemElement>["getVirtualItems"]
|
|
32
|
+
>;
|
|
33
|
+
totalSize: () => number;
|
|
34
|
+
} {
|
|
35
|
+
let version = $state(0);
|
|
36
|
+
|
|
37
|
+
const virtualizer = new Virtualizer<TScrollElement, TItemElement>({
|
|
38
|
+
observeElementRect: observeElementRect,
|
|
39
|
+
observeElementOffset: observeElementOffset,
|
|
40
|
+
scrollToFn: elementScroll,
|
|
41
|
+
...options,
|
|
42
|
+
onChange: (instance, sync) => {
|
|
43
|
+
if (sync) {
|
|
44
|
+
version += 1;
|
|
45
|
+
} else {
|
|
46
|
+
queueMicrotask(() => {
|
|
47
|
+
version += 1;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
options.onChange?.(instance, sync);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
$effect(() => {
|
|
55
|
+
const cleanup = virtualizer._didMount();
|
|
56
|
+
untrack(() => {
|
|
57
|
+
version += 1;
|
|
58
|
+
});
|
|
59
|
+
return cleanup;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
let prev_count = 0;
|
|
63
|
+
|
|
64
|
+
$effect(() => {
|
|
65
|
+
const current_count = options.count;
|
|
66
|
+
virtualizer.setOptions({
|
|
67
|
+
observeElementRect: observeElementRect,
|
|
68
|
+
observeElementOffset: observeElementOffset,
|
|
69
|
+
scrollToFn: elementScroll,
|
|
70
|
+
...options,
|
|
71
|
+
onChange: (instance, sync) => {
|
|
72
|
+
if (sync) {
|
|
73
|
+
version += 1;
|
|
74
|
+
} else {
|
|
75
|
+
queueMicrotask(() => {
|
|
76
|
+
version += 1;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
options.onChange?.(instance, sync);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (prev_count === 0 && current_count > 0) {
|
|
84
|
+
virtualizer.measure();
|
|
85
|
+
}
|
|
86
|
+
prev_count = current_count;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
$effect.pre(() => {
|
|
90
|
+
void version;
|
|
91
|
+
virtualizer._willUpdate();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
instance: virtualizer,
|
|
96
|
+
virtualItems: () => {
|
|
97
|
+
void version;
|
|
98
|
+
return virtualizer.getVirtualItems();
|
|
99
|
+
},
|
|
100
|
+
totalSize: () => {
|
|
101
|
+
void version;
|
|
102
|
+
return virtualizer.getTotalSize();
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|