@gradio/dataframe 0.13.1 → 0.14.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 +12 -0
- package/Dataframe.stories.svelte +36 -0
- package/Index.svelte +2 -0
- package/dist/Index.svelte +2 -0
- package/dist/Index.svelte.d.ts +4 -0
- package/dist/shared/CellMenu.svelte +21 -9
- package/dist/shared/CellMenu.svelte.d.ts +4 -0
- package/dist/shared/CellMenuIcons.svelte +112 -0
- package/dist/shared/CellMenuIcons.svelte.d.ts +16 -0
- package/dist/shared/Table.svelte +57 -10
- package/dist/shared/Table.svelte.d.ts +1 -0
- package/dist/shared/Toolbar.svelte +46 -6
- package/dist/shared/Toolbar.svelte.d.ts +2 -0
- package/dist/shared/table_utils.d.ts +6 -0
- package/dist/shared/table_utils.js +27 -0
- package/package.json +5 -5
- package/shared/CellMenu.svelte +21 -9
- package/shared/CellMenuIcons.svelte +113 -0
- package/shared/Table.svelte +60 -14
- package/shared/Toolbar.svelte +48 -6
- package/shared/table_utils.ts +38 -0
- package/dist/shared/Arrow.svelte +0 -9
- package/dist/shared/Arrow.svelte.d.ts +0 -16
- package/shared/Arrow.svelte +0 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @gradio/dataframe
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- [#10461](https://github.com/gradio-app/gradio/pull/10461) [`ca7c47e`](https://github.com/gradio-app/gradio/commit/ca7c47e5e50a309cd637c4f928ab90af6355b01d) - Add copy button to dataframe toolbar. Thanks @hannahblair!
|
|
8
|
+
- [#10420](https://github.com/gradio-app/gradio/pull/10420) [`a69b8e8`](https://github.com/gradio-app/gradio/commit/a69b8e83ad7c89c627db2bdd5d25b0142731aaac) - Support column/row deletion in `gr.DataFrame`. Thanks @abidlabs!
|
|
9
|
+
|
|
10
|
+
### Dependency updates
|
|
11
|
+
|
|
12
|
+
- @gradio/upload@0.14.8
|
|
13
|
+
- @gradio/button@0.4.4
|
|
14
|
+
|
|
3
15
|
## 0.13.1
|
|
4
16
|
|
|
5
17
|
### Features
|
package/Dataframe.stories.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
// @ts-nocheck
|
|
2
3
|
import { Meta, Template, Story } from "@storybook/addon-svelte-csf";
|
|
3
4
|
import Table from "./shared/Table.svelte";
|
|
4
5
|
import { within } from "@testing-library/dom";
|
|
@@ -10,6 +11,11 @@
|
|
|
10
11
|
<Meta
|
|
11
12
|
title="Components/DataFrame"
|
|
12
13
|
component={Table}
|
|
14
|
+
parameters={{
|
|
15
|
+
test: {
|
|
16
|
+
dangerouslyIgnoreUnhandledErrors: true // ignore fullscreen permission error
|
|
17
|
+
}
|
|
18
|
+
}}
|
|
13
19
|
argTypes={{
|
|
14
20
|
editable: {
|
|
15
21
|
control: [true, false],
|
|
@@ -220,3 +226,33 @@
|
|
|
220
226
|
show_fullscreen_button: true
|
|
221
227
|
}}
|
|
222
228
|
/>
|
|
229
|
+
|
|
230
|
+
<Story
|
|
231
|
+
name="Dataframe toolbar interactions"
|
|
232
|
+
args={{
|
|
233
|
+
col_count: [3, "dynamic"],
|
|
234
|
+
row_count: [2, "dynamic"],
|
|
235
|
+
headers: ["Math", "Reading", "Writifdsfsng"],
|
|
236
|
+
values: [
|
|
237
|
+
[800, 100, 400],
|
|
238
|
+
[200, 800, 700]
|
|
239
|
+
],
|
|
240
|
+
show_fullscreen_button: true,
|
|
241
|
+
show_copy_button: true
|
|
242
|
+
}}
|
|
243
|
+
play={async ({ canvasElement }) => {
|
|
244
|
+
const canvas = within(canvasElement);
|
|
245
|
+
|
|
246
|
+
const copy_button = canvas.getByRole("button", {
|
|
247
|
+
name: /copy table data/i
|
|
248
|
+
});
|
|
249
|
+
await userEvent.click(copy_button);
|
|
250
|
+
|
|
251
|
+
const fullscreen_button = canvas.getByRole("button", {
|
|
252
|
+
name: /enter fullscreen/i
|
|
253
|
+
});
|
|
254
|
+
await userEvent.click(fullscreen_button);
|
|
255
|
+
|
|
256
|
+
await userEvent.click(fullscreen_button);
|
|
257
|
+
}}
|
|
258
|
+
/>
|
package/Index.svelte
CHANGED
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
export let loading_status: LoadingStatus;
|
|
50
50
|
export let interactive: boolean;
|
|
51
51
|
export let show_fullscreen_button = false;
|
|
52
|
+
export let show_copy_button = false;
|
|
52
53
|
|
|
53
54
|
$: _headers = [...(value.headers || headers)];
|
|
54
55
|
$: cell_values = value.data ? [...value.data] : [];
|
|
@@ -105,5 +106,6 @@
|
|
|
105
106
|
stream_handler={(...args) => gradio.client.stream(...args)}
|
|
106
107
|
bind:value_is_output
|
|
107
108
|
{show_fullscreen_button}
|
|
109
|
+
{show_copy_button}
|
|
108
110
|
/>
|
|
109
111
|
</Block>
|
package/dist/Index.svelte
CHANGED
|
@@ -34,6 +34,7 @@ export let max_height = void 0;
|
|
|
34
34
|
export let loading_status;
|
|
35
35
|
export let interactive;
|
|
36
36
|
export let show_fullscreen_button = false;
|
|
37
|
+
export let show_copy_button = false;
|
|
37
38
|
$:
|
|
38
39
|
_headers = [...value.headers || headers];
|
|
39
40
|
$:
|
|
@@ -88,5 +89,6 @@ $:
|
|
|
88
89
|
stream_handler={(...args) => gradio.client.stream(...args)}
|
|
89
90
|
bind:value_is_output
|
|
90
91
|
{show_fullscreen_button}
|
|
92
|
+
{show_copy_button}
|
|
91
93
|
/>
|
|
92
94
|
</Block>
|
package/dist/Index.svelte.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ declare const __propDef: {
|
|
|
38
38
|
loading_status: LoadingStatus;
|
|
39
39
|
interactive: boolean;
|
|
40
40
|
show_fullscreen_button?: boolean | undefined;
|
|
41
|
+
show_copy_button?: boolean | undefined;
|
|
41
42
|
};
|
|
42
43
|
events: {
|
|
43
44
|
[evt: string]: CustomEvent<any>;
|
|
@@ -135,4 +136,7 @@ export default class Index extends SvelteComponent<IndexProps, IndexEvents, Inde
|
|
|
135
136
|
get show_fullscreen_button(): boolean | undefined;
|
|
136
137
|
/**accessor*/
|
|
137
138
|
set show_fullscreen_button(_: boolean | undefined);
|
|
139
|
+
get show_copy_button(): boolean | undefined;
|
|
140
|
+
/**accessor*/
|
|
141
|
+
set show_copy_button(_: boolean | undefined);
|
|
138
142
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script>import { onMount } from "svelte";
|
|
2
|
-
import
|
|
2
|
+
import CellMenuIcons from "./CellMenuIcons.svelte";
|
|
3
3
|
export let x;
|
|
4
4
|
export let y;
|
|
5
5
|
export let on_add_row_above;
|
|
@@ -9,6 +9,10 @@ export let on_add_column_right;
|
|
|
9
9
|
export let row;
|
|
10
10
|
export let col_count;
|
|
11
11
|
export let row_count;
|
|
12
|
+
export let on_delete_row;
|
|
13
|
+
export let on_delete_col;
|
|
14
|
+
export let can_delete_rows;
|
|
15
|
+
export let can_delete_cols;
|
|
12
16
|
export let i18n;
|
|
13
17
|
let menu_element;
|
|
14
18
|
$:
|
|
@@ -42,23 +46,35 @@ function position_menu() {
|
|
|
42
46
|
<div bind:this={menu_element} class="cell-menu">
|
|
43
47
|
{#if !is_header && can_add_rows}
|
|
44
48
|
<button on:click={() => on_add_row_above()}>
|
|
45
|
-
<
|
|
49
|
+
<CellMenuIcons icon="add-row-above" />
|
|
46
50
|
{i18n("dataframe.add_row_above")}
|
|
47
51
|
</button>
|
|
48
52
|
<button on:click={() => on_add_row_below()}>
|
|
49
|
-
<
|
|
53
|
+
<CellMenuIcons icon="add-row-below" />
|
|
50
54
|
{i18n("dataframe.add_row_below")}
|
|
51
55
|
</button>
|
|
56
|
+
{#if can_delete_rows}
|
|
57
|
+
<button on:click={on_delete_row} class="delete">
|
|
58
|
+
<CellMenuIcons icon="delete-row" />
|
|
59
|
+
{i18n("dataframe.delete_row")}
|
|
60
|
+
</button>
|
|
61
|
+
{/if}
|
|
52
62
|
{/if}
|
|
53
63
|
{#if can_add_columns}
|
|
54
64
|
<button on:click={() => on_add_column_left()}>
|
|
55
|
-
<
|
|
65
|
+
<CellMenuIcons icon="add-column-left" />
|
|
56
66
|
{i18n("dataframe.add_column_left")}
|
|
57
67
|
</button>
|
|
58
68
|
<button on:click={() => on_add_column_right()}>
|
|
59
|
-
<
|
|
69
|
+
<CellMenuIcons icon="add-column-right" />
|
|
60
70
|
{i18n("dataframe.add_column_right")}
|
|
61
71
|
</button>
|
|
72
|
+
{#if can_delete_cols}
|
|
73
|
+
<button on:click={on_delete_col} class="delete">
|
|
74
|
+
<CellMenuIcons icon="delete-column" />
|
|
75
|
+
{i18n("dataframe.delete_column")}
|
|
76
|
+
</button>
|
|
77
|
+
{/if}
|
|
62
78
|
{/if}
|
|
63
79
|
</div>
|
|
64
80
|
|
|
@@ -102,8 +118,4 @@ function position_menu() {
|
|
|
102
118
|
fill: currentColor;
|
|
103
119
|
transition: fill 0.2s;
|
|
104
120
|
}
|
|
105
|
-
|
|
106
|
-
.cell-menu button:hover :global(svg) {
|
|
107
|
-
fill: var(--color-accent);
|
|
108
|
-
}
|
|
109
121
|
</style>
|
|
@@ -11,6 +11,10 @@ declare const __propDef: {
|
|
|
11
11
|
row: number;
|
|
12
12
|
col_count: [number, "fixed" | "dynamic"];
|
|
13
13
|
row_count: [number, "fixed" | "dynamic"];
|
|
14
|
+
on_delete_row: () => void;
|
|
15
|
+
on_delete_col: () => void;
|
|
16
|
+
can_delete_rows: boolean;
|
|
17
|
+
can_delete_cols: boolean;
|
|
14
18
|
i18n: I18nFormatter;
|
|
15
19
|
};
|
|
16
20
|
events: {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<script>export let icon;
|
|
2
|
+
</script>
|
|
3
|
+
|
|
4
|
+
{#if icon == "add-column-right"}
|
|
5
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
6
|
+
<rect
|
|
7
|
+
x="4"
|
|
8
|
+
y="6"
|
|
9
|
+
width="4"
|
|
10
|
+
height="12"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
stroke-width="2"
|
|
13
|
+
fill="none"
|
|
14
|
+
/>
|
|
15
|
+
<path
|
|
16
|
+
d="M12 12H19M16 8L19 12L16 16"
|
|
17
|
+
stroke="currentColor"
|
|
18
|
+
stroke-width="2"
|
|
19
|
+
fill="none"
|
|
20
|
+
stroke-linecap="round"
|
|
21
|
+
/>
|
|
22
|
+
</svg>
|
|
23
|
+
{:else if icon == "add-column-left"}
|
|
24
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
25
|
+
<rect
|
|
26
|
+
x="16"
|
|
27
|
+
y="6"
|
|
28
|
+
width="4"
|
|
29
|
+
height="12"
|
|
30
|
+
stroke="currentColor"
|
|
31
|
+
stroke-width="2"
|
|
32
|
+
fill="none"
|
|
33
|
+
/>
|
|
34
|
+
<path
|
|
35
|
+
d="M12 12H5M8 8L5 12L8 16"
|
|
36
|
+
stroke="currentColor"
|
|
37
|
+
stroke-width="2"
|
|
38
|
+
fill="none"
|
|
39
|
+
stroke-linecap="round"
|
|
40
|
+
/>
|
|
41
|
+
</svg>
|
|
42
|
+
{:else if icon == "add-row-above"}
|
|
43
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
44
|
+
<rect
|
|
45
|
+
x="6"
|
|
46
|
+
y="16"
|
|
47
|
+
width="12"
|
|
48
|
+
height="4"
|
|
49
|
+
stroke="currentColor"
|
|
50
|
+
stroke-width="2"
|
|
51
|
+
/>
|
|
52
|
+
<path
|
|
53
|
+
d="M12 12V5M8 8L12 5L16 8"
|
|
54
|
+
stroke="currentColor"
|
|
55
|
+
stroke-width="2"
|
|
56
|
+
fill="none"
|
|
57
|
+
stroke-linecap="round"
|
|
58
|
+
/>
|
|
59
|
+
</svg>
|
|
60
|
+
{:else if icon == "add-row-below"}
|
|
61
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
62
|
+
<rect
|
|
63
|
+
x="6"
|
|
64
|
+
y="4"
|
|
65
|
+
width="12"
|
|
66
|
+
height="4"
|
|
67
|
+
stroke="currentColor"
|
|
68
|
+
stroke-width="2"
|
|
69
|
+
/>
|
|
70
|
+
<path
|
|
71
|
+
d="M12 12V19M8 16L12 19L16 16"
|
|
72
|
+
stroke="currentColor"
|
|
73
|
+
stroke-width="2"
|
|
74
|
+
fill="none"
|
|
75
|
+
stroke-linecap="round"
|
|
76
|
+
/>
|
|
77
|
+
</svg>
|
|
78
|
+
{:else if icon == "delete-row"}
|
|
79
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
80
|
+
<rect
|
|
81
|
+
x="5"
|
|
82
|
+
y="10"
|
|
83
|
+
width="14"
|
|
84
|
+
height="4"
|
|
85
|
+
stroke="currentColor"
|
|
86
|
+
stroke-width="2"
|
|
87
|
+
/>
|
|
88
|
+
<path
|
|
89
|
+
d="M8 7L16 17M16 7L8 17"
|
|
90
|
+
stroke="currentColor"
|
|
91
|
+
stroke-width="2"
|
|
92
|
+
stroke-linecap="round"
|
|
93
|
+
/>
|
|
94
|
+
</svg>
|
|
95
|
+
{:else if icon == "delete-column"}
|
|
96
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
97
|
+
<rect
|
|
98
|
+
x="10"
|
|
99
|
+
y="5"
|
|
100
|
+
width="4"
|
|
101
|
+
height="14"
|
|
102
|
+
stroke="currentColor"
|
|
103
|
+
stroke-width="2"
|
|
104
|
+
/>
|
|
105
|
+
<path
|
|
106
|
+
d="M7 8L17 16M17 8L7 16"
|
|
107
|
+
stroke="currentColor"
|
|
108
|
+
stroke-width="2"
|
|
109
|
+
stroke-linecap="round"
|
|
110
|
+
/>
|
|
111
|
+
</svg>
|
|
112
|
+
{/if}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
declare const __propDef: {
|
|
3
|
+
props: {
|
|
4
|
+
icon: string;
|
|
5
|
+
};
|
|
6
|
+
events: {
|
|
7
|
+
[evt: string]: CustomEvent<any>;
|
|
8
|
+
};
|
|
9
|
+
slots: {};
|
|
10
|
+
};
|
|
11
|
+
export type CellMenuIconsProps = typeof __propDef.props;
|
|
12
|
+
export type CellMenuIconsEvents = typeof __propDef.events;
|
|
13
|
+
export type CellMenuIconsSlots = typeof __propDef.slots;
|
|
14
|
+
export default class CellMenuIcons extends SvelteComponent<CellMenuIconsProps, CellMenuIconsEvents, CellMenuIconsSlots> {
|
|
15
|
+
}
|
|
16
|
+
export {};
|
package/dist/shared/Table.svelte
CHANGED
|
@@ -7,6 +7,7 @@ import {} from "@gradio/client";
|
|
|
7
7
|
import VirtualTable from "./VirtualTable.svelte";
|
|
8
8
|
import CellMenu from "./CellMenu.svelte";
|
|
9
9
|
import Toolbar from "./Toolbar.svelte";
|
|
10
|
+
import { copy_table_data } from "./table_utils";
|
|
10
11
|
export let datatype;
|
|
11
12
|
export let label = null;
|
|
12
13
|
export let show_label = true;
|
|
@@ -26,6 +27,7 @@ export let show_row_numbers = false;
|
|
|
26
27
|
export let upload;
|
|
27
28
|
export let stream_handler;
|
|
28
29
|
export let show_fullscreen_button = false;
|
|
30
|
+
export let show_copy_button = false;
|
|
29
31
|
export let value_is_output = false;
|
|
30
32
|
let selected = false;
|
|
31
33
|
let clicked_cell = void 0;
|
|
@@ -75,9 +77,7 @@ function make_headers(_head) {
|
|
|
75
77
|
}
|
|
76
78
|
function process_data(_values) {
|
|
77
79
|
const data_row_length = _values.length;
|
|
78
|
-
return Array(
|
|
79
|
-
row_count[1] === "fixed" ? row_count[0] : data_row_length < row_count[0] ? row_count[0] : data_row_length
|
|
80
|
-
).fill(0).map(
|
|
80
|
+
return Array(row_count[1] === "fixed" ? row_count[0] : data_row_length).fill(0).map(
|
|
81
81
|
(_, i) => Array(
|
|
82
82
|
col_count[1] === "fixed" ? col_count[0] : data_row_length > 0 ? _values[0].length : headers.length
|
|
83
83
|
).fill(0).map((_2, j) => {
|
|
@@ -574,6 +574,9 @@ function toggle_fullscreen() {
|
|
|
574
574
|
function handle_fullscreen_change() {
|
|
575
575
|
is_fullscreen = !!document.fullscreenElement;
|
|
576
576
|
}
|
|
577
|
+
async function handle_copy() {
|
|
578
|
+
await copy_table_data(data, _headers);
|
|
579
|
+
}
|
|
577
580
|
function toggle_header_menu(event, col) {
|
|
578
581
|
event.stopPropagation();
|
|
579
582
|
if (active_header_menu && active_header_menu.col === col) {
|
|
@@ -593,6 +596,40 @@ function toggle_header_menu(event, col) {
|
|
|
593
596
|
afterUpdate(() => {
|
|
594
597
|
value_is_output = false;
|
|
595
598
|
});
|
|
599
|
+
async function delete_row(index) {
|
|
600
|
+
parent.focus();
|
|
601
|
+
if (row_count[1] !== "dynamic")
|
|
602
|
+
return;
|
|
603
|
+
if (data.length <= 1)
|
|
604
|
+
return;
|
|
605
|
+
data.splice(index, 1);
|
|
606
|
+
data = data;
|
|
607
|
+
selected = false;
|
|
608
|
+
}
|
|
609
|
+
async function delete_col(index) {
|
|
610
|
+
parent.focus();
|
|
611
|
+
if (col_count[1] !== "dynamic")
|
|
612
|
+
return;
|
|
613
|
+
if (data[0].length <= 1)
|
|
614
|
+
return;
|
|
615
|
+
_headers.splice(index, 1);
|
|
616
|
+
_headers = _headers;
|
|
617
|
+
data.forEach((row) => {
|
|
618
|
+
row.splice(index, 1);
|
|
619
|
+
});
|
|
620
|
+
data = data;
|
|
621
|
+
selected = false;
|
|
622
|
+
}
|
|
623
|
+
function delete_row_at(index) {
|
|
624
|
+
delete_row(index);
|
|
625
|
+
active_cell_menu = null;
|
|
626
|
+
active_header_menu = null;
|
|
627
|
+
}
|
|
628
|
+
function delete_col_at(index) {
|
|
629
|
+
delete_col(index);
|
|
630
|
+
active_cell_menu = null;
|
|
631
|
+
active_header_menu = null;
|
|
632
|
+
}
|
|
596
633
|
</script>
|
|
597
634
|
|
|
598
635
|
<svelte:window on:resize={() => set_cell_widths()} />
|
|
@@ -608,6 +645,8 @@ afterUpdate(() => {
|
|
|
608
645
|
{show_fullscreen_button}
|
|
609
646
|
{is_fullscreen}
|
|
610
647
|
on:click={toggle_fullscreen}
|
|
648
|
+
on_copy={handle_copy}
|
|
649
|
+
{show_copy_button}
|
|
611
650
|
/>
|
|
612
651
|
</div>
|
|
613
652
|
<div
|
|
@@ -852,18 +891,22 @@ afterUpdate(() => {
|
|
|
852
891
|
</div>
|
|
853
892
|
</div>
|
|
854
893
|
|
|
855
|
-
{#if active_cell_menu
|
|
894
|
+
{#if active_cell_menu}
|
|
856
895
|
<CellMenu
|
|
857
|
-
{i18n}
|
|
858
896
|
x={active_cell_menu.x}
|
|
859
897
|
y={active_cell_menu.y}
|
|
860
|
-
row={active_cell_menu
|
|
898
|
+
row={active_cell_menu.row}
|
|
861
899
|
{col_count}
|
|
862
900
|
{row_count}
|
|
863
|
-
on_add_row_above={() => add_row_at(active_cell_menu?.row
|
|
864
|
-
on_add_row_below={() => add_row_at(active_cell_menu?.row
|
|
865
|
-
on_add_column_left={() => add_col_at(active_cell_menu?.col
|
|
866
|
-
on_add_column_right={() => add_col_at(active_cell_menu?.col
|
|
901
|
+
on_add_row_above={() => add_row_at(active_cell_menu?.row || 0, "above")}
|
|
902
|
+
on_add_row_below={() => add_row_at(active_cell_menu?.row || 0, "below")}
|
|
903
|
+
on_add_column_left={() => add_col_at(active_cell_menu?.col || 0, "left")}
|
|
904
|
+
on_add_column_right={() => add_col_at(active_cell_menu?.col || 0, "right")}
|
|
905
|
+
on_delete_row={() => delete_row_at(active_cell_menu?.row || 0)}
|
|
906
|
+
on_delete_col={() => delete_col_at(active_cell_menu?.col || 0)}
|
|
907
|
+
can_delete_rows={data.length > 1}
|
|
908
|
+
can_delete_cols={data[0].length > 1}
|
|
909
|
+
{i18n}
|
|
867
910
|
/>
|
|
868
911
|
{/if}
|
|
869
912
|
|
|
@@ -880,6 +923,10 @@ afterUpdate(() => {
|
|
|
880
923
|
on_add_column_left={() => add_col_at(active_header_menu?.col ?? -1, "left")}
|
|
881
924
|
on_add_column_right={() =>
|
|
882
925
|
add_col_at(active_header_menu?.col ?? -1, "right")}
|
|
926
|
+
on_delete_row={() => delete_row_at(active_cell_menu?.row ?? -1)}
|
|
927
|
+
on_delete_col={() => delete_col_at(active_header_menu?.col ?? -1)}
|
|
928
|
+
can_delete_rows={false}
|
|
929
|
+
can_delete_cols={data[0].length > 1}
|
|
883
930
|
/>
|
|
884
931
|
{/if}
|
|
885
932
|
|
|
@@ -28,6 +28,7 @@ declare const __propDef: {
|
|
|
28
28
|
upload: Client["upload"];
|
|
29
29
|
stream_handler: Client["stream"];
|
|
30
30
|
show_fullscreen_button?: boolean | undefined;
|
|
31
|
+
show_copy_button?: boolean | undefined;
|
|
31
32
|
value_is_output?: boolean | undefined;
|
|
32
33
|
display_value?: (string[][] | null) | undefined;
|
|
33
34
|
styling?: (string[][] | null) | undefined;
|
|
@@ -1,19 +1,59 @@
|
|
|
1
|
-
<script>import { Maximize, Minimize } from "@gradio/icons";
|
|
1
|
+
<script>import { Maximize, Minimize, Copy, Check } from "@gradio/icons";
|
|
2
|
+
import { onDestroy } from "svelte";
|
|
2
3
|
export let show_fullscreen_button = false;
|
|
4
|
+
export let show_copy_button = false;
|
|
3
5
|
export let is_fullscreen = false;
|
|
6
|
+
export let on_copy;
|
|
7
|
+
let copied = false;
|
|
8
|
+
let timer;
|
|
9
|
+
function copy_feedback() {
|
|
10
|
+
copied = true;
|
|
11
|
+
if (timer)
|
|
12
|
+
clearTimeout(timer);
|
|
13
|
+
timer = setTimeout(() => {
|
|
14
|
+
copied = false;
|
|
15
|
+
}, 2e3);
|
|
16
|
+
}
|
|
17
|
+
async function handle_copy() {
|
|
18
|
+
await on_copy();
|
|
19
|
+
copy_feedback();
|
|
20
|
+
}
|
|
21
|
+
onDestroy(() => {
|
|
22
|
+
if (timer)
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
});
|
|
4
25
|
</script>
|
|
5
26
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<button
|
|
27
|
+
<div class="toolbar" role="toolbar" aria-label="Table actions">
|
|
28
|
+
{#if show_copy_button}
|
|
29
|
+
<button
|
|
30
|
+
class="toolbar-button"
|
|
31
|
+
on:click={handle_copy}
|
|
32
|
+
aria-label={copied ? "Copied to clipboard" : "Copy table data"}
|
|
33
|
+
title={copied ? "Copied to clipboard" : "Copy table data"}
|
|
34
|
+
>
|
|
35
|
+
{#if copied}
|
|
36
|
+
<Check />
|
|
37
|
+
{:else}
|
|
38
|
+
<Copy />
|
|
39
|
+
{/if}
|
|
40
|
+
</button>
|
|
41
|
+
{/if}
|
|
42
|
+
{#if show_fullscreen_button}
|
|
43
|
+
<button
|
|
44
|
+
class="toolbar-button"
|
|
45
|
+
on:click
|
|
46
|
+
aria-label={is_fullscreen ? "Exit fullscreen" : "Enter fullscreen"}
|
|
47
|
+
title={is_fullscreen ? "Exit fullscreen" : "Enter fullscreen"}
|
|
48
|
+
>
|
|
9
49
|
{#if is_fullscreen}
|
|
10
50
|
<Minimize />
|
|
11
51
|
{:else}
|
|
12
52
|
<Maximize />
|
|
13
53
|
{/if}
|
|
14
54
|
</button>
|
|
15
|
-
|
|
16
|
-
|
|
55
|
+
{/if}
|
|
56
|
+
</div>
|
|
17
57
|
|
|
18
58
|
<style>
|
|
19
59
|
.toolbar {
|
|
@@ -2,7 +2,9 @@ import { SvelteComponent } from "svelte";
|
|
|
2
2
|
declare const __propDef: {
|
|
3
3
|
props: {
|
|
4
4
|
show_fullscreen_button?: boolean | undefined;
|
|
5
|
+
show_copy_button?: boolean | undefined;
|
|
5
6
|
is_fullscreen?: boolean | undefined;
|
|
7
|
+
on_copy: () => Promise<void>;
|
|
6
8
|
};
|
|
7
9
|
events: {
|
|
8
10
|
click: MouseEvent;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export async function copy_table_data(data, headers) {
|
|
2
|
+
const header_row = headers
|
|
3
|
+
? headers.map((h) => String(h.value)).join(",")
|
|
4
|
+
: "";
|
|
5
|
+
const table_data = data
|
|
6
|
+
.map((row) => row.map((cell) => String(cell.value)).join(","))
|
|
7
|
+
.join("\n");
|
|
8
|
+
const all_data = header_row ? `${header_row}\n${table_data}` : table_data;
|
|
9
|
+
try {
|
|
10
|
+
if ("clipboard" in navigator) {
|
|
11
|
+
await navigator.clipboard.writeText(all_data);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
const textArea = document.createElement("textarea");
|
|
15
|
+
textArea.value = all_data;
|
|
16
|
+
textArea.style.position = "absolute";
|
|
17
|
+
textArea.style.left = "-999999px";
|
|
18
|
+
document.body.prepend(textArea);
|
|
19
|
+
textArea.select();
|
|
20
|
+
document.execCommand("copy");
|
|
21
|
+
textArea.remove();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.error("Failed to copy table data:", error);
|
|
26
|
+
}
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/dataframe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Gradio UI packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
"dompurify": "^3.0.3",
|
|
18
18
|
"katex": "^0.16.7",
|
|
19
19
|
"marked": "^12.0.0",
|
|
20
|
-
"@gradio/button": "^0.4.3",
|
|
21
|
-
"@gradio/client": "^1.10.0",
|
|
22
20
|
"@gradio/atoms": "^0.13.1",
|
|
21
|
+
"@gradio/button": "^0.4.4",
|
|
22
|
+
"@gradio/client": "^1.10.0",
|
|
23
23
|
"@gradio/icons": "^0.10.0",
|
|
24
24
|
"@gradio/statustracker": "^0.10.2",
|
|
25
|
-
"@gradio/upload": "^0.14.7",
|
|
26
25
|
"@gradio/markdown-code": "^0.3.0",
|
|
27
|
-
"@gradio/utils": "^0.10.0"
|
|
26
|
+
"@gradio/utils": "^0.10.0",
|
|
27
|
+
"@gradio/upload": "^0.14.8"
|
|
28
28
|
},
|
|
29
29
|
"exports": {
|
|
30
30
|
".": {
|
package/shared/CellMenu.svelte
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from "svelte";
|
|
3
|
-
import
|
|
3
|
+
import CellMenuIcons from "./CellMenuIcons.svelte";
|
|
4
4
|
import type { I18nFormatter } from "js/utils/src";
|
|
5
5
|
|
|
6
6
|
export let x: number;
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
export let row: number;
|
|
13
13
|
export let col_count: [number, "fixed" | "dynamic"];
|
|
14
14
|
export let row_count: [number, "fixed" | "dynamic"];
|
|
15
|
+
export let on_delete_row: () => void;
|
|
16
|
+
export let on_delete_col: () => void;
|
|
17
|
+
export let can_delete_rows: boolean;
|
|
18
|
+
export let can_delete_cols: boolean;
|
|
15
19
|
|
|
16
20
|
export let i18n: I18nFormatter;
|
|
17
21
|
let menu_element: HTMLDivElement;
|
|
@@ -50,23 +54,35 @@
|
|
|
50
54
|
<div bind:this={menu_element} class="cell-menu">
|
|
51
55
|
{#if !is_header && can_add_rows}
|
|
52
56
|
<button on:click={() => on_add_row_above()}>
|
|
53
|
-
<
|
|
57
|
+
<CellMenuIcons icon="add-row-above" />
|
|
54
58
|
{i18n("dataframe.add_row_above")}
|
|
55
59
|
</button>
|
|
56
60
|
<button on:click={() => on_add_row_below()}>
|
|
57
|
-
<
|
|
61
|
+
<CellMenuIcons icon="add-row-below" />
|
|
58
62
|
{i18n("dataframe.add_row_below")}
|
|
59
63
|
</button>
|
|
64
|
+
{#if can_delete_rows}
|
|
65
|
+
<button on:click={on_delete_row} class="delete">
|
|
66
|
+
<CellMenuIcons icon="delete-row" />
|
|
67
|
+
{i18n("dataframe.delete_row")}
|
|
68
|
+
</button>
|
|
69
|
+
{/if}
|
|
60
70
|
{/if}
|
|
61
71
|
{#if can_add_columns}
|
|
62
72
|
<button on:click={() => on_add_column_left()}>
|
|
63
|
-
<
|
|
73
|
+
<CellMenuIcons icon="add-column-left" />
|
|
64
74
|
{i18n("dataframe.add_column_left")}
|
|
65
75
|
</button>
|
|
66
76
|
<button on:click={() => on_add_column_right()}>
|
|
67
|
-
<
|
|
77
|
+
<CellMenuIcons icon="add-column-right" />
|
|
68
78
|
{i18n("dataframe.add_column_right")}
|
|
69
79
|
</button>
|
|
80
|
+
{#if can_delete_cols}
|
|
81
|
+
<button on:click={on_delete_col} class="delete">
|
|
82
|
+
<CellMenuIcons icon="delete-column" />
|
|
83
|
+
{i18n("dataframe.delete_column")}
|
|
84
|
+
</button>
|
|
85
|
+
{/if}
|
|
70
86
|
{/if}
|
|
71
87
|
</div>
|
|
72
88
|
|
|
@@ -110,8 +126,4 @@
|
|
|
110
126
|
fill: currentColor;
|
|
111
127
|
transition: fill 0.2s;
|
|
112
128
|
}
|
|
113
|
-
|
|
114
|
-
.cell-menu button:hover :global(svg) {
|
|
115
|
-
fill: var(--color-accent);
|
|
116
|
-
}
|
|
117
129
|
</style>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export let icon: string;
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
{#if icon == "add-column-right"}
|
|
6
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
7
|
+
<rect
|
|
8
|
+
x="4"
|
|
9
|
+
y="6"
|
|
10
|
+
width="4"
|
|
11
|
+
height="12"
|
|
12
|
+
stroke="currentColor"
|
|
13
|
+
stroke-width="2"
|
|
14
|
+
fill="none"
|
|
15
|
+
/>
|
|
16
|
+
<path
|
|
17
|
+
d="M12 12H19M16 8L19 12L16 16"
|
|
18
|
+
stroke="currentColor"
|
|
19
|
+
stroke-width="2"
|
|
20
|
+
fill="none"
|
|
21
|
+
stroke-linecap="round"
|
|
22
|
+
/>
|
|
23
|
+
</svg>
|
|
24
|
+
{:else if icon == "add-column-left"}
|
|
25
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
26
|
+
<rect
|
|
27
|
+
x="16"
|
|
28
|
+
y="6"
|
|
29
|
+
width="4"
|
|
30
|
+
height="12"
|
|
31
|
+
stroke="currentColor"
|
|
32
|
+
stroke-width="2"
|
|
33
|
+
fill="none"
|
|
34
|
+
/>
|
|
35
|
+
<path
|
|
36
|
+
d="M12 12H5M8 8L5 12L8 16"
|
|
37
|
+
stroke="currentColor"
|
|
38
|
+
stroke-width="2"
|
|
39
|
+
fill="none"
|
|
40
|
+
stroke-linecap="round"
|
|
41
|
+
/>
|
|
42
|
+
</svg>
|
|
43
|
+
{:else if icon == "add-row-above"}
|
|
44
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
45
|
+
<rect
|
|
46
|
+
x="6"
|
|
47
|
+
y="16"
|
|
48
|
+
width="12"
|
|
49
|
+
height="4"
|
|
50
|
+
stroke="currentColor"
|
|
51
|
+
stroke-width="2"
|
|
52
|
+
/>
|
|
53
|
+
<path
|
|
54
|
+
d="M12 12V5M8 8L12 5L16 8"
|
|
55
|
+
stroke="currentColor"
|
|
56
|
+
stroke-width="2"
|
|
57
|
+
fill="none"
|
|
58
|
+
stroke-linecap="round"
|
|
59
|
+
/>
|
|
60
|
+
</svg>
|
|
61
|
+
{:else if icon == "add-row-below"}
|
|
62
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
63
|
+
<rect
|
|
64
|
+
x="6"
|
|
65
|
+
y="4"
|
|
66
|
+
width="12"
|
|
67
|
+
height="4"
|
|
68
|
+
stroke="currentColor"
|
|
69
|
+
stroke-width="2"
|
|
70
|
+
/>
|
|
71
|
+
<path
|
|
72
|
+
d="M12 12V19M8 16L12 19L16 16"
|
|
73
|
+
stroke="currentColor"
|
|
74
|
+
stroke-width="2"
|
|
75
|
+
fill="none"
|
|
76
|
+
stroke-linecap="round"
|
|
77
|
+
/>
|
|
78
|
+
</svg>
|
|
79
|
+
{:else if icon == "delete-row"}
|
|
80
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
81
|
+
<rect
|
|
82
|
+
x="5"
|
|
83
|
+
y="10"
|
|
84
|
+
width="14"
|
|
85
|
+
height="4"
|
|
86
|
+
stroke="currentColor"
|
|
87
|
+
stroke-width="2"
|
|
88
|
+
/>
|
|
89
|
+
<path
|
|
90
|
+
d="M8 7L16 17M16 7L8 17"
|
|
91
|
+
stroke="currentColor"
|
|
92
|
+
stroke-width="2"
|
|
93
|
+
stroke-linecap="round"
|
|
94
|
+
/>
|
|
95
|
+
</svg>
|
|
96
|
+
{:else if icon == "delete-column"}
|
|
97
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
98
|
+
<rect
|
|
99
|
+
x="10"
|
|
100
|
+
y="5"
|
|
101
|
+
width="4"
|
|
102
|
+
height="14"
|
|
103
|
+
stroke="currentColor"
|
|
104
|
+
stroke-width="2"
|
|
105
|
+
/>
|
|
106
|
+
<path
|
|
107
|
+
d="M7 8L17 16M17 8L7 16"
|
|
108
|
+
stroke="currentColor"
|
|
109
|
+
stroke-width="2"
|
|
110
|
+
stroke-linecap="round"
|
|
111
|
+
/>
|
|
112
|
+
</svg>
|
|
113
|
+
{/if}
|
package/shared/Table.svelte
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
} from "./utils";
|
|
18
18
|
import CellMenu from "./CellMenu.svelte";
|
|
19
19
|
import Toolbar from "./Toolbar.svelte";
|
|
20
|
+
import { copy_table_data } from "./table_utils";
|
|
20
21
|
|
|
21
22
|
export let datatype: Datatype | Datatype[];
|
|
22
23
|
export let label: string | null = null;
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
export let upload: Client["upload"];
|
|
44
45
|
export let stream_handler: Client["stream"];
|
|
45
46
|
export let show_fullscreen_button = false;
|
|
47
|
+
export let show_copy_button = false;
|
|
46
48
|
export let value_is_output = false;
|
|
47
49
|
|
|
48
50
|
let selected: false | [number, number] = false;
|
|
@@ -120,13 +122,7 @@
|
|
|
120
122
|
id: string;
|
|
121
123
|
}[][] {
|
|
122
124
|
const data_row_length = _values.length;
|
|
123
|
-
return Array(
|
|
124
|
-
row_count[1] === "fixed"
|
|
125
|
-
? row_count[0]
|
|
126
|
-
: data_row_length < row_count[0]
|
|
127
|
-
? row_count[0]
|
|
128
|
-
: data_row_length
|
|
129
|
-
)
|
|
125
|
+
return Array(row_count[1] === "fixed" ? row_count[0] : data_row_length)
|
|
130
126
|
.fill(0)
|
|
131
127
|
.map((_, i) =>
|
|
132
128
|
Array(
|
|
@@ -771,6 +767,10 @@
|
|
|
771
767
|
is_fullscreen = !!document.fullscreenElement;
|
|
772
768
|
}
|
|
773
769
|
|
|
770
|
+
async function handle_copy(): Promise<void> {
|
|
771
|
+
await copy_table_data(data, _headers);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
774
|
function toggle_header_menu(event: MouseEvent, col: number): void {
|
|
775
775
|
event.stopPropagation();
|
|
776
776
|
if (active_header_menu && active_header_menu.col === col) {
|
|
@@ -791,6 +791,42 @@
|
|
|
791
791
|
afterUpdate(() => {
|
|
792
792
|
value_is_output = false;
|
|
793
793
|
});
|
|
794
|
+
|
|
795
|
+
async function delete_row(index: number): Promise<void> {
|
|
796
|
+
parent.focus();
|
|
797
|
+
if (row_count[1] !== "dynamic") return;
|
|
798
|
+
if (data.length <= 1) return;
|
|
799
|
+
data.splice(index, 1);
|
|
800
|
+
data = data;
|
|
801
|
+
selected = false;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
async function delete_col(index: number): Promise<void> {
|
|
805
|
+
parent.focus();
|
|
806
|
+
if (col_count[1] !== "dynamic") return;
|
|
807
|
+
if (data[0].length <= 1) return;
|
|
808
|
+
|
|
809
|
+
_headers.splice(index, 1);
|
|
810
|
+
_headers = _headers;
|
|
811
|
+
|
|
812
|
+
data.forEach((row) => {
|
|
813
|
+
row.splice(index, 1);
|
|
814
|
+
});
|
|
815
|
+
data = data;
|
|
816
|
+
selected = false;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function delete_row_at(index: number): void {
|
|
820
|
+
delete_row(index);
|
|
821
|
+
active_cell_menu = null;
|
|
822
|
+
active_header_menu = null;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function delete_col_at(index: number): void {
|
|
826
|
+
delete_col(index);
|
|
827
|
+
active_cell_menu = null;
|
|
828
|
+
active_header_menu = null;
|
|
829
|
+
}
|
|
794
830
|
</script>
|
|
795
831
|
|
|
796
832
|
<svelte:window on:resize={() => set_cell_widths()} />
|
|
@@ -806,6 +842,8 @@
|
|
|
806
842
|
{show_fullscreen_button}
|
|
807
843
|
{is_fullscreen}
|
|
808
844
|
on:click={toggle_fullscreen}
|
|
845
|
+
on_copy={handle_copy}
|
|
846
|
+
{show_copy_button}
|
|
809
847
|
/>
|
|
810
848
|
</div>
|
|
811
849
|
<div
|
|
@@ -1050,18 +1088,22 @@
|
|
|
1050
1088
|
</div>
|
|
1051
1089
|
</div>
|
|
1052
1090
|
|
|
1053
|
-
{#if active_cell_menu
|
|
1091
|
+
{#if active_cell_menu}
|
|
1054
1092
|
<CellMenu
|
|
1055
|
-
{i18n}
|
|
1056
1093
|
x={active_cell_menu.x}
|
|
1057
1094
|
y={active_cell_menu.y}
|
|
1058
|
-
row={active_cell_menu
|
|
1095
|
+
row={active_cell_menu.row}
|
|
1059
1096
|
{col_count}
|
|
1060
1097
|
{row_count}
|
|
1061
|
-
on_add_row_above={() => add_row_at(active_cell_menu?.row
|
|
1062
|
-
on_add_row_below={() => add_row_at(active_cell_menu?.row
|
|
1063
|
-
on_add_column_left={() => add_col_at(active_cell_menu?.col
|
|
1064
|
-
on_add_column_right={() => add_col_at(active_cell_menu?.col
|
|
1098
|
+
on_add_row_above={() => add_row_at(active_cell_menu?.row || 0, "above")}
|
|
1099
|
+
on_add_row_below={() => add_row_at(active_cell_menu?.row || 0, "below")}
|
|
1100
|
+
on_add_column_left={() => add_col_at(active_cell_menu?.col || 0, "left")}
|
|
1101
|
+
on_add_column_right={() => add_col_at(active_cell_menu?.col || 0, "right")}
|
|
1102
|
+
on_delete_row={() => delete_row_at(active_cell_menu?.row || 0)}
|
|
1103
|
+
on_delete_col={() => delete_col_at(active_cell_menu?.col || 0)}
|
|
1104
|
+
can_delete_rows={data.length > 1}
|
|
1105
|
+
can_delete_cols={data[0].length > 1}
|
|
1106
|
+
{i18n}
|
|
1065
1107
|
/>
|
|
1066
1108
|
{/if}
|
|
1067
1109
|
|
|
@@ -1078,6 +1120,10 @@
|
|
|
1078
1120
|
on_add_column_left={() => add_col_at(active_header_menu?.col ?? -1, "left")}
|
|
1079
1121
|
on_add_column_right={() =>
|
|
1080
1122
|
add_col_at(active_header_menu?.col ?? -1, "right")}
|
|
1123
|
+
on_delete_row={() => delete_row_at(active_cell_menu?.row ?? -1)}
|
|
1124
|
+
on_delete_col={() => delete_col_at(active_header_menu?.col ?? -1)}
|
|
1125
|
+
can_delete_rows={false}
|
|
1126
|
+
can_delete_cols={data[0].length > 1}
|
|
1081
1127
|
/>
|
|
1082
1128
|
{/if}
|
|
1083
1129
|
|
package/shared/Toolbar.svelte
CHANGED
|
@@ -1,21 +1,63 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Maximize, Minimize } from "@gradio/icons";
|
|
2
|
+
import { Maximize, Minimize, Copy, Check } from "@gradio/icons";
|
|
3
|
+
import { onDestroy } from "svelte";
|
|
3
4
|
|
|
4
5
|
export let show_fullscreen_button = false;
|
|
6
|
+
export let show_copy_button = false;
|
|
5
7
|
export let is_fullscreen = false;
|
|
8
|
+
export let on_copy: () => Promise<void>;
|
|
9
|
+
|
|
10
|
+
let copied = false;
|
|
11
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
12
|
+
|
|
13
|
+
function copy_feedback(): void {
|
|
14
|
+
copied = true;
|
|
15
|
+
if (timer) clearTimeout(timer);
|
|
16
|
+
timer = setTimeout(() => {
|
|
17
|
+
copied = false;
|
|
18
|
+
}, 2000);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function handle_copy(): Promise<void> {
|
|
22
|
+
await on_copy();
|
|
23
|
+
copy_feedback();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
onDestroy(() => {
|
|
27
|
+
if (timer) clearTimeout(timer);
|
|
28
|
+
});
|
|
6
29
|
</script>
|
|
7
30
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
<button
|
|
31
|
+
<div class="toolbar" role="toolbar" aria-label="Table actions">
|
|
32
|
+
{#if show_copy_button}
|
|
33
|
+
<button
|
|
34
|
+
class="toolbar-button"
|
|
35
|
+
on:click={handle_copy}
|
|
36
|
+
aria-label={copied ? "Copied to clipboard" : "Copy table data"}
|
|
37
|
+
title={copied ? "Copied to clipboard" : "Copy table data"}
|
|
38
|
+
>
|
|
39
|
+
{#if copied}
|
|
40
|
+
<Check />
|
|
41
|
+
{:else}
|
|
42
|
+
<Copy />
|
|
43
|
+
{/if}
|
|
44
|
+
</button>
|
|
45
|
+
{/if}
|
|
46
|
+
{#if show_fullscreen_button}
|
|
47
|
+
<button
|
|
48
|
+
class="toolbar-button"
|
|
49
|
+
on:click
|
|
50
|
+
aria-label={is_fullscreen ? "Exit fullscreen" : "Enter fullscreen"}
|
|
51
|
+
title={is_fullscreen ? "Exit fullscreen" : "Enter fullscreen"}
|
|
52
|
+
>
|
|
11
53
|
{#if is_fullscreen}
|
|
12
54
|
<Minimize />
|
|
13
55
|
{:else}
|
|
14
56
|
<Maximize />
|
|
15
57
|
{/if}
|
|
16
58
|
</button>
|
|
17
|
-
|
|
18
|
-
|
|
59
|
+
{/if}
|
|
60
|
+
</div>
|
|
19
61
|
|
|
20
62
|
<style>
|
|
21
63
|
.toolbar {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { HeadersWithIDs } from "./utils";
|
|
2
|
+
|
|
3
|
+
export type TableData = {
|
|
4
|
+
value: string | number;
|
|
5
|
+
id: string;
|
|
6
|
+
}[][];
|
|
7
|
+
|
|
8
|
+
export async function copy_table_data(
|
|
9
|
+
data: TableData,
|
|
10
|
+
headers?: HeadersWithIDs
|
|
11
|
+
): Promise<void> {
|
|
12
|
+
const header_row = headers
|
|
13
|
+
? headers.map((h) => String(h.value)).join(",")
|
|
14
|
+
: "";
|
|
15
|
+
const table_data = data
|
|
16
|
+
.map((row) => row.map((cell) => String(cell.value)).join(","))
|
|
17
|
+
.join("\n");
|
|
18
|
+
|
|
19
|
+
const all_data = header_row ? `${header_row}\n${table_data}` : table_data;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
if ("clipboard" in navigator) {
|
|
23
|
+
await navigator.clipboard.writeText(all_data);
|
|
24
|
+
} else {
|
|
25
|
+
const textArea = document.createElement("textarea");
|
|
26
|
+
textArea.value = all_data;
|
|
27
|
+
textArea.style.position = "absolute";
|
|
28
|
+
textArea.style.left = "-999999px";
|
|
29
|
+
document.body.prepend(textArea);
|
|
30
|
+
textArea.select();
|
|
31
|
+
|
|
32
|
+
document.execCommand("copy");
|
|
33
|
+
textArea.remove();
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error("Failed to copy table data:", error);
|
|
37
|
+
}
|
|
38
|
+
}
|
package/dist/shared/Arrow.svelte
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { SvelteComponent } from "svelte";
|
|
2
|
-
declare const __propDef: {
|
|
3
|
-
props: {
|
|
4
|
-
transform: string;
|
|
5
|
-
};
|
|
6
|
-
events: {
|
|
7
|
-
[evt: string]: CustomEvent<any>;
|
|
8
|
-
};
|
|
9
|
-
slots: {};
|
|
10
|
-
};
|
|
11
|
-
export type ArrowProps = typeof __propDef.props;
|
|
12
|
-
export type ArrowEvents = typeof __propDef.events;
|
|
13
|
-
export type ArrowSlots = typeof __propDef.slots;
|
|
14
|
-
export default class Arrow extends SvelteComponent<ArrowProps, ArrowEvents, ArrowSlots> {
|
|
15
|
-
}
|
|
16
|
-
export {};
|