@gradio/dataframe 0.13.1 → 0.15.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 +28 -0
- package/Dataframe.stories.svelte +148 -2
- package/Index.svelte +4 -0
- package/dist/Index.svelte +4 -0
- package/dist/Index.svelte.d.ts +8 -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/EditableCell.svelte +52 -7
- package/dist/shared/EditableCell.svelte.d.ts +1 -1
- package/dist/shared/Table.svelte +356 -270
- package/dist/shared/Table.svelte.d.ts +2 -0
- package/dist/shared/Toolbar.svelte +46 -6
- package/dist/shared/Toolbar.svelte.d.ts +2 -0
- package/dist/shared/selection_utils.d.ts +20 -0
- package/dist/shared/selection_utils.js +111 -0
- package/dist/shared/table_utils.d.ts +12 -0
- package/dist/shared/table_utils.js +113 -0
- package/dist/shared/types.d.ts +2 -0
- package/dist/shared/types.js +1 -0
- package/package.json +6 -6
- package/shared/CellMenu.svelte +21 -9
- package/shared/CellMenuIcons.svelte +113 -0
- package/shared/EditableCell.svelte +58 -7
- package/shared/Table.svelte +400 -341
- package/shared/Toolbar.svelte +48 -6
- package/shared/selection_utils.ts +188 -0
- package/shared/table_utils.ts +148 -0
- package/shared/types.ts +2 -0
- package/dist/shared/Arrow.svelte +0 -9
- package/dist/shared/Arrow.svelte.d.ts +0 -16
- package/shared/Arrow.svelte +0 -10
|
@@ -28,7 +28,9 @@ 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;
|
|
33
|
+
max_chars?: number | undefined;
|
|
32
34
|
display_value?: (string[][] | null) | undefined;
|
|
33
35
|
styling?: (string[][] | null) | undefined;
|
|
34
36
|
};
|
|
@@ -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,20 @@
|
|
|
1
|
+
export type CellCoordinate = [number, number];
|
|
2
|
+
export type CellData = {
|
|
3
|
+
id: string;
|
|
4
|
+
value: string | number;
|
|
5
|
+
};
|
|
6
|
+
export type EditingState = false | CellCoordinate;
|
|
7
|
+
export declare function is_cell_selected(cell: CellCoordinate, selected_cells: CellCoordinate[]): string;
|
|
8
|
+
export declare function get_range_selection(start: CellCoordinate, end: CellCoordinate): CellCoordinate[];
|
|
9
|
+
export declare function handle_selection(current: CellCoordinate, selected_cells: CellCoordinate[], event: {
|
|
10
|
+
shiftKey: boolean;
|
|
11
|
+
metaKey: boolean;
|
|
12
|
+
ctrlKey: boolean;
|
|
13
|
+
}): CellCoordinate[];
|
|
14
|
+
export declare function handle_delete_key(data: CellData[][], selected_cells: CellCoordinate[]): CellData[][];
|
|
15
|
+
export declare function handle_editing_state(current: CellCoordinate, editing: EditingState, selected_cells: CellCoordinate[], editable: boolean): EditingState;
|
|
16
|
+
export declare function should_show_cell_menu(cell: CellCoordinate, selected_cells: CellCoordinate[], editable: boolean): boolean;
|
|
17
|
+
export declare function get_next_cell_coordinates(current: CellCoordinate, data: CellData[][], shift_key: boolean): CellCoordinate | false;
|
|
18
|
+
export declare function move_cursor(key: "ArrowRight" | "ArrowLeft" | "ArrowDown" | "ArrowUp", current_coords: CellCoordinate, data: CellData[][]): CellCoordinate | false;
|
|
19
|
+
export declare function get_current_indices(id: string, data: CellData[][]): [number, number];
|
|
20
|
+
export declare function handle_click_outside(event: Event, parent: HTMLElement): boolean;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export function is_cell_selected(cell, selected_cells) {
|
|
2
|
+
const [row, col] = cell;
|
|
3
|
+
if (!selected_cells.some(([r, c]) => r === row && c === col))
|
|
4
|
+
return "";
|
|
5
|
+
const up = selected_cells.some(([r, c]) => r === row - 1 && c === col);
|
|
6
|
+
const down = selected_cells.some(([r, c]) => r === row + 1 && c === col);
|
|
7
|
+
const left = selected_cells.some(([r, c]) => r === row && c === col - 1);
|
|
8
|
+
const right = selected_cells.some(([r, c]) => r === row && c === col + 1);
|
|
9
|
+
return `cell-selected${up ? " no-top" : ""}${down ? " no-bottom" : ""}${left ? " no-left" : ""}${right ? " no-right" : ""}`;
|
|
10
|
+
}
|
|
11
|
+
export function get_range_selection(start, end) {
|
|
12
|
+
const [start_row, start_col] = start;
|
|
13
|
+
const [end_row, end_col] = end;
|
|
14
|
+
const min_row = Math.min(start_row, end_row);
|
|
15
|
+
const max_row = Math.max(start_row, end_row);
|
|
16
|
+
const min_col = Math.min(start_col, end_col);
|
|
17
|
+
const max_col = Math.max(start_col, end_col);
|
|
18
|
+
const cells = [];
|
|
19
|
+
for (let i = min_row; i <= max_row; i++) {
|
|
20
|
+
for (let j = min_col; j <= max_col; j++) {
|
|
21
|
+
cells.push([i, j]);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return cells;
|
|
25
|
+
}
|
|
26
|
+
export function handle_selection(current, selected_cells, event) {
|
|
27
|
+
if (event.shiftKey && selected_cells.length > 0) {
|
|
28
|
+
return get_range_selection(selected_cells[selected_cells.length - 1], current);
|
|
29
|
+
}
|
|
30
|
+
if (event.metaKey || event.ctrlKey) {
|
|
31
|
+
const index = selected_cells.findIndex(([r, c]) => r === current[0] && c === current[1]);
|
|
32
|
+
if (index === -1) {
|
|
33
|
+
return [...selected_cells, current];
|
|
34
|
+
}
|
|
35
|
+
return selected_cells.filter((_, i) => i !== index);
|
|
36
|
+
}
|
|
37
|
+
return [current];
|
|
38
|
+
}
|
|
39
|
+
export function handle_delete_key(data, selected_cells) {
|
|
40
|
+
const new_data = data.map((row) => [...row]);
|
|
41
|
+
selected_cells.forEach(([row, col]) => {
|
|
42
|
+
if (new_data[row] && new_data[row][col]) {
|
|
43
|
+
new_data[row][col] = { ...new_data[row][col], value: "" };
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return new_data;
|
|
47
|
+
}
|
|
48
|
+
export function handle_editing_state(current, editing, selected_cells, editable) {
|
|
49
|
+
const [row, col] = current;
|
|
50
|
+
if (!editable)
|
|
51
|
+
return false;
|
|
52
|
+
if (editing && editing[0] === row && editing[1] === col)
|
|
53
|
+
return editing;
|
|
54
|
+
if (selected_cells.length === 1 &&
|
|
55
|
+
selected_cells[0][0] === row &&
|
|
56
|
+
selected_cells[0][1] === col) {
|
|
57
|
+
return [row, col];
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
export function should_show_cell_menu(cell, selected_cells, editable) {
|
|
62
|
+
const [row, col] = cell;
|
|
63
|
+
return (editable &&
|
|
64
|
+
selected_cells.length === 1 &&
|
|
65
|
+
selected_cells[0][0] === row &&
|
|
66
|
+
selected_cells[0][1] === col);
|
|
67
|
+
}
|
|
68
|
+
export function get_next_cell_coordinates(current, data, shift_key) {
|
|
69
|
+
const [row, col] = current;
|
|
70
|
+
const direction = shift_key ? -1 : 1;
|
|
71
|
+
if (data[row]?.[col + direction]) {
|
|
72
|
+
return [row, col + direction];
|
|
73
|
+
}
|
|
74
|
+
const next_row = row + (direction > 0 ? 1 : 0);
|
|
75
|
+
const prev_row = row + (direction < 0 ? -1 : 0);
|
|
76
|
+
if (direction > 0 && data[next_row]?.[0]) {
|
|
77
|
+
return [next_row, 0];
|
|
78
|
+
}
|
|
79
|
+
if (direction < 0 && data[prev_row]?.[data[0].length - 1]) {
|
|
80
|
+
return [prev_row, data[0].length - 1];
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
export function move_cursor(key, current_coords, data) {
|
|
85
|
+
const dir = {
|
|
86
|
+
ArrowRight: [0, 1],
|
|
87
|
+
ArrowLeft: [0, -1],
|
|
88
|
+
ArrowDown: [1, 0],
|
|
89
|
+
ArrowUp: [-1, 0]
|
|
90
|
+
}[key];
|
|
91
|
+
const i = current_coords[0] + dir[0];
|
|
92
|
+
const j = current_coords[1] + dir[1];
|
|
93
|
+
if (i < 0 && j <= 0) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const is_data = data[i]?.[j];
|
|
97
|
+
if (is_data) {
|
|
98
|
+
return [i, j];
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
export function get_current_indices(id, data) {
|
|
103
|
+
return data.reduce((acc, arr, i) => {
|
|
104
|
+
const j = arr.reduce((_acc, _data, k) => (id === _data.id ? k : _acc), -1);
|
|
105
|
+
return j === -1 ? acc : [i, j];
|
|
106
|
+
}, [-1, -1]);
|
|
107
|
+
}
|
|
108
|
+
export function handle_click_outside(event, parent) {
|
|
109
|
+
const [trigger] = event.composedPath();
|
|
110
|
+
return !parent.contains(trigger);
|
|
111
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HeadersWithIDs } from "./utils";
|
|
2
|
+
import type { CellCoordinate } from "./selection_utils";
|
|
3
|
+
export type TableData = {
|
|
4
|
+
value: string | number;
|
|
5
|
+
id: string;
|
|
6
|
+
}[][];
|
|
7
|
+
export declare function get_max(_d: TableData): TableData[0];
|
|
8
|
+
export declare function guess_delimiter(text: string, possibleDelimiters: string[]): string[];
|
|
9
|
+
export declare function data_uri_to_blob(data_uri: string): Blob;
|
|
10
|
+
export declare function copy_table_data(data: TableData, headers?: HeadersWithIDs, selected_cells?: CellCoordinate[]): Promise<void>;
|
|
11
|
+
export declare function blob_to_string(blob: Blob, col_count: [number, "fixed" | "dynamic"], make_headers: (head: string[]) => HeadersWithIDs, set_values: (values: (string | number)[][]) => void): void;
|
|
12
|
+
export declare function handle_file_upload(data_uri: string, col_count: [number, "fixed" | "dynamic"], make_headers: (head: string[]) => HeadersWithIDs, set_values: (values: (string | number)[][]) => void): void;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { dsvFormat } from "d3-dsv";
|
|
2
|
+
export function get_max(_d) {
|
|
3
|
+
if (!_d || _d.length === 0 || !_d[0])
|
|
4
|
+
return [];
|
|
5
|
+
let max = _d[0].slice();
|
|
6
|
+
for (let i = 0; i < _d.length; i++) {
|
|
7
|
+
for (let j = 0; j < _d[i].length; j++) {
|
|
8
|
+
if (`${max[j].value}`.length < `${_d[i][j].value}`.length) {
|
|
9
|
+
max[j] = _d[i][j];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return max;
|
|
14
|
+
}
|
|
15
|
+
export function guess_delimiter(text, possibleDelimiters) {
|
|
16
|
+
return possibleDelimiters.filter(weedOut);
|
|
17
|
+
function weedOut(delimiter) {
|
|
18
|
+
var cache = -1;
|
|
19
|
+
return text.split("\n").every(checkLength);
|
|
20
|
+
function checkLength(line) {
|
|
21
|
+
if (!line)
|
|
22
|
+
return true;
|
|
23
|
+
var length = line.split(delimiter).length;
|
|
24
|
+
if (cache < 0)
|
|
25
|
+
cache = length;
|
|
26
|
+
return cache === length && length > 1;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function data_uri_to_blob(data_uri) {
|
|
31
|
+
const byte_str = atob(data_uri.split(",")[1]);
|
|
32
|
+
const mime_str = data_uri.split(",")[0].split(":")[1].split(";")[0];
|
|
33
|
+
const ab = new ArrayBuffer(byte_str.length);
|
|
34
|
+
const ia = new Uint8Array(ab);
|
|
35
|
+
for (let i = 0; i < byte_str.length; i++) {
|
|
36
|
+
ia[i] = byte_str.charCodeAt(i);
|
|
37
|
+
}
|
|
38
|
+
return new Blob([ab], { type: mime_str });
|
|
39
|
+
}
|
|
40
|
+
export async function copy_table_data(data, headers, selected_cells) {
|
|
41
|
+
if (!selected_cells || selected_cells.length === 0) {
|
|
42
|
+
const header_row = headers
|
|
43
|
+
? headers.map((h) => String(h.value)).join(",")
|
|
44
|
+
: "";
|
|
45
|
+
const table_data = data
|
|
46
|
+
.map((row) => row.map((cell) => String(cell.value)).join(","))
|
|
47
|
+
.join("\n");
|
|
48
|
+
const all_data = header_row ? `${header_row}\n${table_data}` : table_data;
|
|
49
|
+
await write_to_clipboard(all_data);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const min_row = Math.min(...selected_cells.map(([r]) => r));
|
|
53
|
+
const max_row = Math.max(...selected_cells.map(([r]) => r));
|
|
54
|
+
const min_col = Math.min(...selected_cells.map(([_, c]) => c));
|
|
55
|
+
const max_col = Math.max(...selected_cells.map(([_, c]) => c));
|
|
56
|
+
const selected_data = [];
|
|
57
|
+
for (let i = min_row; i <= max_row; i++) {
|
|
58
|
+
const row = [];
|
|
59
|
+
for (let j = min_col; j <= max_col; j++) {
|
|
60
|
+
const is_selected = selected_cells.some(([r, c]) => r === i && c === j);
|
|
61
|
+
row.push(is_selected ? String(data[i][j].value) : "");
|
|
62
|
+
}
|
|
63
|
+
selected_data.push(row.join(","));
|
|
64
|
+
}
|
|
65
|
+
await write_to_clipboard(selected_data.join("\n"));
|
|
66
|
+
}
|
|
67
|
+
async function write_to_clipboard(csv_data) {
|
|
68
|
+
try {
|
|
69
|
+
if ("clipboard" in navigator) {
|
|
70
|
+
await navigator.clipboard.writeText(csv_data);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const textArea = document.createElement("textarea");
|
|
74
|
+
textArea.value = csv_data;
|
|
75
|
+
textArea.style.position = "absolute";
|
|
76
|
+
textArea.style.left = "-999999px";
|
|
77
|
+
document.body.prepend(textArea);
|
|
78
|
+
textArea.select();
|
|
79
|
+
document.execCommand("copy");
|
|
80
|
+
textArea.remove();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
console.error("Failed to copy table data:", error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export function blob_to_string(blob, col_count, make_headers, set_values) {
|
|
88
|
+
const reader = new FileReader();
|
|
89
|
+
function handle_read(e) {
|
|
90
|
+
if (!e?.target?.result || typeof e.target.result !== "string")
|
|
91
|
+
return;
|
|
92
|
+
const [delimiter] = guess_delimiter(e.target.result, [",", "\t"]);
|
|
93
|
+
const [head, ...rest] = dsvFormat(delimiter).parseRows(e.target.result);
|
|
94
|
+
make_headers(col_count[1] === "fixed" ? head.slice(0, col_count[0]) : head);
|
|
95
|
+
set_values(rest);
|
|
96
|
+
reader.removeEventListener("loadend", handle_read);
|
|
97
|
+
}
|
|
98
|
+
reader.addEventListener("loadend", handle_read);
|
|
99
|
+
reader.readAsText(blob);
|
|
100
|
+
}
|
|
101
|
+
export function handle_file_upload(data_uri, col_count, make_headers, set_values) {
|
|
102
|
+
const blob = data_uri_to_blob(data_uri);
|
|
103
|
+
const reader = new FileReader();
|
|
104
|
+
reader.addEventListener("loadend", (e) => {
|
|
105
|
+
if (!e?.target?.result || typeof e.target.result !== "string")
|
|
106
|
+
return;
|
|
107
|
+
const [delimiter] = guess_delimiter(e.target.result, [",", "\t"]);
|
|
108
|
+
const [head, ...rest] = dsvFormat(delimiter).parseRows(e.target.result);
|
|
109
|
+
make_headers(col_count[1] === "fixed" ? head.slice(0, col_count[0]) : head);
|
|
110
|
+
set_values(rest);
|
|
111
|
+
});
|
|
112
|
+
reader.readAsText(blob);
|
|
113
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/dataframe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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",
|
|
23
21
|
"@gradio/icons": "^0.10.0",
|
|
24
|
-
"@gradio/statustracker": "^0.10.2",
|
|
25
|
-
"@gradio/upload": "^0.14.7",
|
|
26
22
|
"@gradio/markdown-code": "^0.3.0",
|
|
27
|
-
"@gradio/
|
|
23
|
+
"@gradio/statustracker": "^0.10.2",
|
|
24
|
+
"@gradio/button": "^0.4.5",
|
|
25
|
+
"@gradio/upload": "^0.15.0",
|
|
26
|
+
"@gradio/utils": "^0.10.0",
|
|
27
|
+
"@gradio/client": "^1.11.0"
|
|
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}
|
|
@@ -23,12 +23,27 @@
|
|
|
23
23
|
export let line_breaks = true;
|
|
24
24
|
export let editable = true;
|
|
25
25
|
export let root: string;
|
|
26
|
+
export let max_chars: number | null = null;
|
|
26
27
|
|
|
27
28
|
const dispatch = createEventDispatcher();
|
|
29
|
+
let is_expanded = false;
|
|
28
30
|
|
|
29
31
|
export let el: HTMLInputElement | null;
|
|
30
32
|
$: _value = value;
|
|
31
33
|
|
|
34
|
+
function truncate_text(
|
|
35
|
+
text: string | number,
|
|
36
|
+
max_length: number | null = null
|
|
37
|
+
): string {
|
|
38
|
+
const str = String(text);
|
|
39
|
+
if (!max_length || str.length <= max_length) return str;
|
|
40
|
+
return str.slice(0, max_length) + "...";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
$: display_text = is_expanded
|
|
44
|
+
? value
|
|
45
|
+
: truncate_text(display_value || value, max_chars);
|
|
46
|
+
|
|
32
47
|
function use_focus(node: HTMLInputElement): any {
|
|
33
48
|
if (clear_on_focus) {
|
|
34
49
|
_value = "";
|
|
@@ -52,11 +67,21 @@
|
|
|
52
67
|
|
|
53
68
|
function handle_keydown(event: KeyboardEvent): void {
|
|
54
69
|
if (event.key === "Enter") {
|
|
55
|
-
|
|
56
|
-
|
|
70
|
+
if (edit) {
|
|
71
|
+
value = _value;
|
|
72
|
+
dispatch("blur");
|
|
73
|
+
} else if (!header) {
|
|
74
|
+
is_expanded = !is_expanded;
|
|
75
|
+
}
|
|
57
76
|
}
|
|
58
77
|
dispatch("keydown", event);
|
|
59
78
|
}
|
|
79
|
+
|
|
80
|
+
function handle_click(): void {
|
|
81
|
+
if (!edit && !header) {
|
|
82
|
+
is_expanded = !is_expanded;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
60
85
|
</script>
|
|
61
86
|
|
|
62
87
|
{#if edit}
|
|
@@ -76,27 +101,31 @@
|
|
|
76
101
|
{/if}
|
|
77
102
|
|
|
78
103
|
<span
|
|
79
|
-
on:
|
|
80
|
-
|
|
104
|
+
on:click={handle_click}
|
|
105
|
+
on:keydown={handle_keydown}
|
|
106
|
+
tabindex="0"
|
|
81
107
|
role="button"
|
|
82
108
|
class:edit
|
|
109
|
+
class:expanded={is_expanded}
|
|
110
|
+
class:multiline={header}
|
|
83
111
|
on:focus|preventDefault
|
|
84
112
|
style={styling}
|
|
85
113
|
class="table-cell-text"
|
|
114
|
+
data-editable={editable}
|
|
86
115
|
placeholder=" "
|
|
87
116
|
>
|
|
88
117
|
{#if datatype === "html"}
|
|
89
|
-
{@html
|
|
118
|
+
{@html display_text}
|
|
90
119
|
{:else if datatype === "markdown"}
|
|
91
120
|
<MarkdownCode
|
|
92
|
-
message={
|
|
121
|
+
message={display_text.toLocaleString()}
|
|
93
122
|
{latex_delimiters}
|
|
94
123
|
{line_breaks}
|
|
95
124
|
chatbot={false}
|
|
96
125
|
{root}
|
|
97
126
|
/>
|
|
98
127
|
{:else}
|
|
99
|
-
{editable ?
|
|
128
|
+
{editable ? display_text : display_value || display_text}
|
|
100
129
|
{/if}
|
|
101
130
|
</span>
|
|
102
131
|
|
|
@@ -117,6 +146,8 @@
|
|
|
117
146
|
|
|
118
147
|
span {
|
|
119
148
|
flex: 1 1 0%;
|
|
149
|
+
position: relative;
|
|
150
|
+
display: inline-block;
|
|
120
151
|
outline: none;
|
|
121
152
|
padding: var(--size-2);
|
|
122
153
|
-webkit-user-select: text;
|
|
@@ -124,11 +155,31 @@
|
|
|
124
155
|
-ms-user-select: text;
|
|
125
156
|
user-select: text;
|
|
126
157
|
cursor: text;
|
|
158
|
+
width: 100%;
|
|
159
|
+
height: 100%;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
input:where(:not(.header), [data-editable="true"]) {
|
|
163
|
+
width: calc(100% - var(--size-10));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
span.expanded {
|
|
167
|
+
height: auto;
|
|
168
|
+
min-height: 100%;
|
|
169
|
+
white-space: pre-wrap;
|
|
170
|
+
word-break: break-word;
|
|
171
|
+
white-space: normal;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.multiline {
|
|
175
|
+
white-space: pre-line;
|
|
127
176
|
}
|
|
128
177
|
|
|
129
178
|
.header {
|
|
130
179
|
transform: translateX(0);
|
|
131
180
|
font-weight: var(--weight-bold);
|
|
181
|
+
white-space: normal;
|
|
182
|
+
word-break: break-word;
|
|
132
183
|
}
|
|
133
184
|
|
|
134
185
|
.edit {
|