@gradio/dataframe 0.16.5 → 0.17.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 +34 -0
- package/Dataframe.stories.svelte +202 -9
- package/Index.svelte +7 -13
- package/dist/Index.svelte +5 -9
- package/dist/Index.svelte.d.ts +9 -2
- package/dist/shared/CellMenu.svelte +91 -10
- package/dist/shared/CellMenu.svelte.d.ts +6 -0
- package/dist/shared/CellMenuButton.svelte +45 -0
- package/dist/shared/CellMenuButton.svelte.d.ts +16 -0
- package/dist/shared/CellMenuIcons.svelte +79 -0
- package/dist/shared/EditableCell.svelte +83 -14
- package/dist/shared/EditableCell.svelte.d.ts +12 -3
- package/dist/shared/EmptyRowButton.svelte +28 -0
- package/dist/shared/EmptyRowButton.svelte.d.ts +16 -0
- package/dist/shared/RowNumber.svelte +40 -0
- package/dist/shared/RowNumber.svelte.d.ts +17 -0
- package/dist/shared/Table.svelte +564 -1121
- package/dist/shared/Table.svelte.d.ts +4 -0
- package/dist/shared/TableCell.svelte +291 -0
- package/dist/shared/TableCell.svelte.d.ts +57 -0
- package/dist/shared/TableHeader.svelte +239 -0
- package/dist/shared/TableHeader.svelte.d.ts +45 -0
- package/dist/shared/Toolbar.svelte +18 -8
- package/dist/shared/VirtualTable.svelte +66 -19
- package/dist/shared/VirtualTable.svelte.d.ts +4 -0
- package/dist/shared/context/keyboard_context.d.ts +37 -0
- package/dist/shared/context/keyboard_context.js +12 -0
- package/dist/shared/context/selection_context.d.ts +32 -0
- package/dist/shared/context/selection_context.js +107 -0
- package/dist/shared/context/table_context.d.ts +141 -0
- package/dist/shared/context/table_context.js +375 -0
- package/dist/shared/icons/Padlock.svelte +24 -0
- package/dist/shared/icons/Padlock.svelte.d.ts +23 -0
- package/dist/shared/icons/SelectionButtons.svelte +85 -0
- package/dist/shared/icons/SelectionButtons.svelte.d.ts +18 -0
- package/dist/shared/icons/SortArrowDown.svelte +24 -0
- package/dist/shared/icons/SortArrowDown.svelte.d.ts +16 -0
- package/dist/shared/icons/SortArrowUp.svelte +24 -0
- package/dist/shared/icons/SortArrowUp.svelte.d.ts +16 -0
- package/dist/shared/icons/SortButtonDown.svelte +14 -0
- package/dist/shared/icons/SortButtonDown.svelte.d.ts +23 -0
- package/dist/shared/icons/SortButtonUp.svelte +15 -0
- package/dist/shared/icons/SortButtonUp.svelte.d.ts +23 -0
- package/dist/shared/icons/SortIcon.svelte +46 -68
- package/dist/shared/icons/SortIcon.svelte.d.ts +3 -2
- package/dist/shared/selection_utils.d.ts +2 -1
- package/dist/shared/selection_utils.js +39 -10
- package/dist/shared/utils/data_processing.d.ts +13 -0
- package/dist/shared/utils/data_processing.js +45 -0
- package/dist/shared/utils/drag_utils.d.ts +15 -0
- package/dist/shared/utils/drag_utils.js +57 -0
- package/dist/shared/utils/keyboard_utils.d.ts +2 -0
- package/dist/shared/utils/keyboard_utils.js +186 -0
- package/dist/shared/utils/sort_utils.d.ts +22 -3
- package/dist/shared/utils/sort_utils.js +44 -24
- package/dist/shared/utils/table_utils.d.ts +6 -5
- package/dist/shared/utils/table_utils.js +13 -56
- package/package.json +7 -7
- package/shared/CellMenu.svelte +90 -10
- package/shared/CellMenuButton.svelte +46 -0
- package/shared/CellMenuIcons.svelte +79 -0
- package/shared/EditableCell.svelte +97 -18
- package/shared/EmptyRowButton.svelte +29 -0
- package/shared/RowNumber.svelte +41 -0
- package/shared/Table.svelte +604 -1235
- package/shared/TableCell.svelte +324 -0
- package/shared/TableHeader.svelte +256 -0
- package/shared/Toolbar.svelte +19 -8
- package/shared/VirtualTable.svelte +72 -19
- package/shared/context/keyboard_context.ts +65 -0
- package/shared/context/selection_context.ts +168 -0
- package/shared/context/table_context.ts +625 -0
- package/shared/icons/Padlock.svelte +24 -0
- package/shared/icons/SelectionButtons.svelte +93 -0
- package/shared/icons/SortArrowDown.svelte +25 -0
- package/shared/icons/SortArrowUp.svelte +25 -0
- package/shared/icons/SortButtonDown.svelte +14 -0
- package/shared/icons/SortButtonUp.svelte +15 -0
- package/shared/icons/SortIcon.svelte +47 -70
- package/shared/selection_utils.ts +39 -13
- package/shared/utils/data_processing.ts +72 -0
- package/shared/utils/drag_utils.ts +92 -0
- package/shared/utils/keyboard_utils.ts +238 -0
- package/shared/utils/sort_utils.test.ts +262 -14
- package/shared/utils/sort_utils.ts +67 -31
- package/shared/utils/table_utils.test.ts +66 -45
- package/shared/utils/table_utils.ts +16 -86
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export let position: "column" | "row";
|
|
3
|
+
export let coords: [number, number];
|
|
4
|
+
export let on_click: (() => void) | null = null;
|
|
5
|
+
|
|
6
|
+
$: is_first_position =
|
|
7
|
+
position === "column" ? coords[0] === 0 : coords[1] === 0;
|
|
8
|
+
$: direction =
|
|
9
|
+
position === "column"
|
|
10
|
+
? is_first_position
|
|
11
|
+
? "down"
|
|
12
|
+
: "up"
|
|
13
|
+
: is_first_position
|
|
14
|
+
? "right"
|
|
15
|
+
: "left";
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<button
|
|
19
|
+
class="selection-button selection-button-{position} {is_first_position
|
|
20
|
+
? `move-${direction}`
|
|
21
|
+
: ''}"
|
|
22
|
+
on:click|stopPropagation={() => on_click && on_click()}
|
|
23
|
+
aria-label={`Select ${position}`}
|
|
24
|
+
>
|
|
25
|
+
<span class={direction}>
|
|
26
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
27
|
+
<path
|
|
28
|
+
d="m16.707 13.293-4-4a1 1 0 0 0-1.414 0l-4 4A1 1 0 0 0 8 15h8a1 1 0 0 0 .707-1.707z"
|
|
29
|
+
data-name={direction}
|
|
30
|
+
/>
|
|
31
|
+
</svg>
|
|
32
|
+
</span>
|
|
33
|
+
</button>
|
|
34
|
+
|
|
35
|
+
<style>
|
|
36
|
+
.selection-button {
|
|
37
|
+
position: absolute;
|
|
38
|
+
background: var(--color-accent);
|
|
39
|
+
width: var(--size-3);
|
|
40
|
+
height: var(--size-5);
|
|
41
|
+
color: var(--background-fill-primary);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.selection-button-column {
|
|
45
|
+
top: -15px;
|
|
46
|
+
left: 50%;
|
|
47
|
+
transform: translateX(-50%) rotate(90deg);
|
|
48
|
+
border-radius: var(--radius-sm) 0 0 var(--radius-sm);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.selection-button-row {
|
|
52
|
+
left: calc(var(--size-2-5) * -1);
|
|
53
|
+
border-radius: var(--radius-sm) 0 0 var(--radius-sm);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.move-down {
|
|
57
|
+
bottom: -14px;
|
|
58
|
+
top: auto;
|
|
59
|
+
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.move-right {
|
|
63
|
+
left: auto;
|
|
64
|
+
right: calc(var(--size-2-5) * -1);
|
|
65
|
+
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
svg {
|
|
69
|
+
fill: currentColor;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
span {
|
|
73
|
+
display: flex;
|
|
74
|
+
width: 100%;
|
|
75
|
+
height: 100%;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.up {
|
|
79
|
+
transform: rotate(-90deg);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.down {
|
|
83
|
+
transform: rotate(90deg);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.left {
|
|
87
|
+
transform: rotate(-90deg);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.right {
|
|
91
|
+
transform: rotate(90deg);
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export let size = 16;
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<svg
|
|
6
|
+
width={size}
|
|
7
|
+
height={size}
|
|
8
|
+
viewBox="0 0 16 16"
|
|
9
|
+
fill="none"
|
|
10
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
11
|
+
>
|
|
12
|
+
<path
|
|
13
|
+
d="M4 8L8 12L12 8"
|
|
14
|
+
stroke="currentColor"
|
|
15
|
+
stroke-width="1.5"
|
|
16
|
+
stroke-linecap="round"
|
|
17
|
+
stroke-linejoin="round"
|
|
18
|
+
/>
|
|
19
|
+
<path
|
|
20
|
+
d="M8 12V4"
|
|
21
|
+
stroke="currentColor"
|
|
22
|
+
stroke-width="1.5"
|
|
23
|
+
stroke-linecap="round"
|
|
24
|
+
/>
|
|
25
|
+
</svg>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export let size = 16;
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<svg
|
|
6
|
+
width={size}
|
|
7
|
+
height={size}
|
|
8
|
+
viewBox="0 0 16 16"
|
|
9
|
+
fill="none"
|
|
10
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
11
|
+
>
|
|
12
|
+
<path
|
|
13
|
+
d="M4 8L8 4L12 8"
|
|
14
|
+
stroke="currentColor"
|
|
15
|
+
stroke-width="1.5"
|
|
16
|
+
stroke-linecap="round"
|
|
17
|
+
stroke-linejoin="round"
|
|
18
|
+
/>
|
|
19
|
+
<path
|
|
20
|
+
d="M8 4V12"
|
|
21
|
+
stroke="currentColor"
|
|
22
|
+
stroke-width="1.5"
|
|
23
|
+
stroke-linecap="round"
|
|
24
|
+
/>
|
|
25
|
+
</svg>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
viewBox="0 0 24 24"
|
|
3
|
+
fill="none"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
aria-hidden="true"
|
|
6
|
+
focusable="false"
|
|
7
|
+
>
|
|
8
|
+
<path
|
|
9
|
+
d="M7 14l5-5 5 5"
|
|
10
|
+
stroke="currentColor"
|
|
11
|
+
stroke-width="2"
|
|
12
|
+
stroke-linecap="round"
|
|
13
|
+
stroke-linejoin="round"
|
|
14
|
+
/>
|
|
15
|
+
</svg>
|
|
@@ -1,61 +1,43 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { createEventDispatcher } from "svelte";
|
|
3
3
|
import type { I18nFormatter } from "@gradio/utils";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import SortButtonUp from "./SortButtonUp.svelte";
|
|
5
|
+
import SortButtonDown from "./SortButtonDown.svelte";
|
|
6
|
+
import { IconButton } from "@gradio/atoms";
|
|
7
|
+
type SortDirection = "asc" | "desc";
|
|
6
8
|
export let direction: SortDirection | null = null;
|
|
9
|
+
export let priority: number | null = null;
|
|
7
10
|
export let i18n: I18nFormatter;
|
|
8
11
|
|
|
9
12
|
const dispatch = createEventDispatcher<{ sort: SortDirection }>();
|
|
10
13
|
</script>
|
|
11
14
|
|
|
12
15
|
<div class="sort-icons" role="group" aria-label={i18n("dataframe.sort_column")}>
|
|
13
|
-
|
|
14
|
-
class="
|
|
15
|
-
|
|
16
|
-
on:click={() => dispatch("sort", "asc")}
|
|
17
|
-
aria-label={i18n("dataframe.sort_ascending")}
|
|
18
|
-
aria-pressed={direction === "asc"}
|
|
19
|
-
>
|
|
20
|
-
<svg
|
|
21
|
-
viewBox="0 0 24 24"
|
|
22
|
-
fill="none"
|
|
23
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
24
|
-
aria-hidden="true"
|
|
25
|
-
focusable="false"
|
|
26
|
-
>
|
|
27
|
-
<path
|
|
28
|
-
d="M7 14l5-5 5 5"
|
|
29
|
-
stroke="currentColor"
|
|
30
|
-
stroke-width="2"
|
|
31
|
-
stroke-linecap="round"
|
|
32
|
-
stroke-linejoin="round"
|
|
33
|
-
/>
|
|
34
|
-
</svg>
|
|
35
|
-
</button>
|
|
36
|
-
<button
|
|
37
|
-
class="sort-button down"
|
|
38
|
-
class:active={direction === "des"}
|
|
39
|
-
on:click={() => dispatch("sort", "des")}
|
|
40
|
-
aria-label={i18n("dataframe.sort_descending")}
|
|
41
|
-
aria-pressed={direction === "des"}
|
|
42
|
-
>
|
|
43
|
-
<svg
|
|
44
|
-
viewBox="0 0 24 24"
|
|
45
|
-
fill="none"
|
|
46
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
47
|
-
aria-hidden="true"
|
|
48
|
-
focusable="false"
|
|
16
|
+
{#if (direction === "asc" || direction === "desc") && priority !== null}
|
|
17
|
+
<span aria-label={`Sort priority: ${priority}`} class="priority"
|
|
18
|
+
>{priority}</span
|
|
49
19
|
>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
20
|
+
{/if}
|
|
21
|
+
<IconButton
|
|
22
|
+
size="x-small"
|
|
23
|
+
label={i18n("dataframe.sort_ascending")}
|
|
24
|
+
Icon={SortButtonUp}
|
|
25
|
+
highlight={direction === "asc"}
|
|
26
|
+
on:click={(event) => {
|
|
27
|
+
event.stopPropagation();
|
|
28
|
+
dispatch("sort", "asc");
|
|
29
|
+
}}
|
|
30
|
+
></IconButton>
|
|
31
|
+
<IconButton
|
|
32
|
+
size="x-small"
|
|
33
|
+
label={i18n("dataframe.sort_descending")}
|
|
34
|
+
Icon={SortButtonDown}
|
|
35
|
+
highlight={direction === "desc"}
|
|
36
|
+
on:click={(event) => {
|
|
37
|
+
event.stopPropagation();
|
|
38
|
+
dispatch("sort", "desc");
|
|
39
|
+
}}
|
|
40
|
+
></IconButton>
|
|
59
41
|
</div>
|
|
60
42
|
|
|
61
43
|
<style>
|
|
@@ -63,33 +45,28 @@
|
|
|
63
45
|
display: flex;
|
|
64
46
|
flex-direction: column;
|
|
65
47
|
gap: 0;
|
|
66
|
-
margin-right: var(--spacing-
|
|
48
|
+
margin-right: var(--spacing-sm);
|
|
67
49
|
}
|
|
68
50
|
|
|
69
|
-
.sort-button {
|
|
51
|
+
.sort-icons :global(button) {
|
|
52
|
+
margin-bottom: var(--spacing-xs);
|
|
53
|
+
border: 1px solid var(--border-color-primary);
|
|
54
|
+
background: unset;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.priority {
|
|
70
58
|
display: flex;
|
|
71
59
|
align-items: center;
|
|
72
60
|
justify-content: center;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.sort-button.active {
|
|
86
|
-
opacity: 1;
|
|
87
|
-
color: var(--color-accent);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
svg {
|
|
91
|
-
width: var(--size-3);
|
|
92
|
-
height: var(--size-3);
|
|
93
|
-
display: block;
|
|
61
|
+
position: absolute;
|
|
62
|
+
font-size: var(--size-2);
|
|
63
|
+
left: 19px;
|
|
64
|
+
z-index: var(--layer-3);
|
|
65
|
+
top: var(--spacing-xs);
|
|
66
|
+
background-color: var(--button-secondary-background-fill);
|
|
67
|
+
color: var(--body-text-color);
|
|
68
|
+
border-radius: var(--radius-sm);
|
|
69
|
+
width: var(--size-2-5);
|
|
70
|
+
height: var(--size-2-5);
|
|
94
71
|
}
|
|
95
72
|
</style>
|
|
@@ -2,6 +2,14 @@ import type { CellCoordinate, EditingState } from "./types";
|
|
|
2
2
|
|
|
3
3
|
export type CellData = { id: string; value: string | number };
|
|
4
4
|
|
|
5
|
+
export function is_cell_in_selection(
|
|
6
|
+
coords: [number, number],
|
|
7
|
+
selected_cells: [number, number][]
|
|
8
|
+
): boolean {
|
|
9
|
+
const [row, col] = coords;
|
|
10
|
+
return selected_cells.some(([r, c]) => r === row && c === col);
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
export function is_cell_selected(
|
|
6
14
|
cell: CellCoordinate,
|
|
7
15
|
selected_cells: CellCoordinate[]
|
|
@@ -29,8 +37,14 @@ export function get_range_selection(
|
|
|
29
37
|
const max_col = Math.max(start_col, end_col);
|
|
30
38
|
|
|
31
39
|
const cells: CellCoordinate[] = [];
|
|
40
|
+
// Add the start cell as the "anchor" cell so that when
|
|
41
|
+
// we press shift+arrow keys, the selection will always
|
|
42
|
+
// include the anchor cell.
|
|
43
|
+
cells.push(start);
|
|
44
|
+
|
|
32
45
|
for (let i = min_row; i <= max_row; i++) {
|
|
33
46
|
for (let j = min_col; j <= max_col; j++) {
|
|
47
|
+
if (i === start_row && j === start_col) continue;
|
|
34
48
|
cells.push([i, j]);
|
|
35
49
|
}
|
|
36
50
|
}
|
|
@@ -137,10 +151,11 @@ export function get_next_cell_coordinates(
|
|
|
137
151
|
}
|
|
138
152
|
|
|
139
153
|
export function move_cursor(
|
|
140
|
-
|
|
154
|
+
event: KeyboardEvent,
|
|
141
155
|
current_coords: CellCoordinate,
|
|
142
156
|
data: CellData[][]
|
|
143
157
|
): CellCoordinate | false {
|
|
158
|
+
const key = event.key as "ArrowRight" | "ArrowLeft" | "ArrowDown" | "ArrowUp";
|
|
144
159
|
const dir = {
|
|
145
160
|
ArrowRight: [0, 1],
|
|
146
161
|
ArrowLeft: [0, -1],
|
|
@@ -148,8 +163,27 @@ export function move_cursor(
|
|
|
148
163
|
ArrowUp: [-1, 0]
|
|
149
164
|
}[key];
|
|
150
165
|
|
|
151
|
-
|
|
152
|
-
|
|
166
|
+
let i, j;
|
|
167
|
+
if (event.metaKey || event.ctrlKey) {
|
|
168
|
+
if (key === "ArrowRight") {
|
|
169
|
+
i = current_coords[0];
|
|
170
|
+
j = data[0].length - 1;
|
|
171
|
+
} else if (key === "ArrowLeft") {
|
|
172
|
+
i = current_coords[0];
|
|
173
|
+
j = 0;
|
|
174
|
+
} else if (key === "ArrowDown") {
|
|
175
|
+
i = data.length - 1;
|
|
176
|
+
j = current_coords[1];
|
|
177
|
+
} else if (key === "ArrowUp") {
|
|
178
|
+
i = 0;
|
|
179
|
+
j = current_coords[1];
|
|
180
|
+
} else {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
i = current_coords[0] + dir[0];
|
|
185
|
+
j = current_coords[1] + dir[1];
|
|
186
|
+
}
|
|
153
187
|
|
|
154
188
|
if (i < 0 && j <= 0) {
|
|
155
189
|
return false;
|
|
@@ -206,25 +240,17 @@ export function calculate_selection_positions(
|
|
|
206
240
|
return { col_pos: "0px", row_pos: undefined };
|
|
207
241
|
}
|
|
208
242
|
|
|
209
|
-
let offset = 0;
|
|
210
|
-
for (let i = 0; i < col; i++) {
|
|
211
|
-
offset += parseFloat(
|
|
212
|
-
getComputedStyle(parent).getPropertyValue(`--cell-width-${i}`)
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
243
|
const cell_id = data[row][col].id;
|
|
217
244
|
const cell_el = els[cell_id]?.cell;
|
|
218
245
|
|
|
219
246
|
if (!cell_el) {
|
|
220
|
-
// if we cant get the row position, just return the column position which is static
|
|
221
247
|
return { col_pos: "0px", row_pos: undefined };
|
|
222
248
|
}
|
|
223
249
|
|
|
224
250
|
const cell_rect = cell_el.getBoundingClientRect();
|
|
225
251
|
const table_rect = table.getBoundingClientRect();
|
|
226
252
|
const col_pos = `${cell_rect.left - table_rect.left + cell_rect.width / 2}px`;
|
|
227
|
-
const
|
|
228
|
-
|
|
253
|
+
const row_pos = `${cell_rect.top - table_rect.top + cell_rect.height / 2}px`;
|
|
254
|
+
|
|
229
255
|
return { col_pos, row_pos };
|
|
230
256
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Headers, HeadersWithIDs } from "../utils";
|
|
2
|
+
|
|
3
|
+
export function make_headers(
|
|
4
|
+
_head: Headers,
|
|
5
|
+
col_count: [number, "fixed" | "dynamic"],
|
|
6
|
+
els: Record<
|
|
7
|
+
string,
|
|
8
|
+
{ cell: null | HTMLTableCellElement; input: null | HTMLInputElement }
|
|
9
|
+
>,
|
|
10
|
+
make_id: () => string
|
|
11
|
+
): HeadersWithIDs {
|
|
12
|
+
let _h = _head || [];
|
|
13
|
+
if (col_count[1] === "fixed" && _h.length < col_count[0]) {
|
|
14
|
+
const fill = Array(col_count[0] - _h.length)
|
|
15
|
+
.fill("")
|
|
16
|
+
.map((_, i) => `${i + _h.length}`);
|
|
17
|
+
_h = _h.concat(fill);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!_h || _h.length === 0) {
|
|
21
|
+
return Array(col_count[0])
|
|
22
|
+
.fill(0)
|
|
23
|
+
.map((_, i) => {
|
|
24
|
+
const _id = make_id();
|
|
25
|
+
els[_id] = { cell: null, input: null };
|
|
26
|
+
return { id: _id, value: JSON.stringify(i + 1) };
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return _h.map((h, i) => {
|
|
31
|
+
const _id = make_id();
|
|
32
|
+
els[_id] = { cell: null, input: null };
|
|
33
|
+
return { id: _id, value: h ?? "" };
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function process_data(
|
|
38
|
+
values: (string | number)[][],
|
|
39
|
+
els: Record<
|
|
40
|
+
string,
|
|
41
|
+
{ cell: null | HTMLTableCellElement; input: null | HTMLInputElement }
|
|
42
|
+
>,
|
|
43
|
+
data_binding: Record<string, any>,
|
|
44
|
+
make_id: () => string,
|
|
45
|
+
display_value: string[][] | null = null
|
|
46
|
+
): { id: string; value: string | number; display_value?: string }[][] {
|
|
47
|
+
if (!values || values.length === 0) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result = values.map((row, i) => {
|
|
52
|
+
return row.map((value, j) => {
|
|
53
|
+
const _id = make_id();
|
|
54
|
+
els[_id] = { cell: null, input: null };
|
|
55
|
+
data_binding[_id] = value;
|
|
56
|
+
|
|
57
|
+
let display = display_value?.[i]?.[j];
|
|
58
|
+
|
|
59
|
+
if (display === undefined) {
|
|
60
|
+
display = String(value);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
id: _id,
|
|
65
|
+
value,
|
|
66
|
+
display_value: display
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { CellCoordinate } from "../types";
|
|
2
|
+
import { get_range_selection } from "../selection_utils";
|
|
3
|
+
|
|
4
|
+
export type DragState = {
|
|
5
|
+
is_dragging: boolean;
|
|
6
|
+
drag_start: CellCoordinate | null;
|
|
7
|
+
mouse_down_pos: { x: number; y: number } | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type DragHandlers = {
|
|
11
|
+
handle_mouse_down: (event: MouseEvent, row: number, col: number) => void;
|
|
12
|
+
handle_mouse_move: (event: MouseEvent) => void;
|
|
13
|
+
handle_mouse_up: (event: MouseEvent) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function create_drag_handlers(
|
|
17
|
+
state: DragState,
|
|
18
|
+
set_is_dragging: (value: boolean) => void,
|
|
19
|
+
set_selected_cells: (cells: CellCoordinate[]) => void,
|
|
20
|
+
set_selected: (cell: CellCoordinate | false) => void,
|
|
21
|
+
handle_cell_click: (event: MouseEvent, row: number, col: number) => void,
|
|
22
|
+
show_row_numbers: boolean,
|
|
23
|
+
parent_element?: HTMLElement
|
|
24
|
+
): DragHandlers {
|
|
25
|
+
const start_drag = (event: MouseEvent, row: number, col: number): void => {
|
|
26
|
+
if (
|
|
27
|
+
event.target instanceof HTMLAnchorElement ||
|
|
28
|
+
(show_row_numbers && col === -1)
|
|
29
|
+
)
|
|
30
|
+
return;
|
|
31
|
+
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
event.stopPropagation();
|
|
34
|
+
|
|
35
|
+
state.mouse_down_pos = { x: event.clientX, y: event.clientY };
|
|
36
|
+
state.drag_start = [row, col];
|
|
37
|
+
|
|
38
|
+
if (!event.shiftKey && !event.metaKey && !event.ctrlKey) {
|
|
39
|
+
set_selected_cells([[row, col]]);
|
|
40
|
+
set_selected([row, col]);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const update_selection = (event: MouseEvent): void => {
|
|
45
|
+
const cell = (event.target as HTMLElement).closest("td");
|
|
46
|
+
if (!cell) return;
|
|
47
|
+
|
|
48
|
+
const row = parseInt(cell.getAttribute("data-row") || "0");
|
|
49
|
+
const col = parseInt(cell.getAttribute("data-col") || "0");
|
|
50
|
+
|
|
51
|
+
if (isNaN(row) || isNaN(col)) return;
|
|
52
|
+
|
|
53
|
+
const selection_range = get_range_selection(state.drag_start!, [row, col]);
|
|
54
|
+
set_selected_cells(selection_range);
|
|
55
|
+
set_selected([row, col]);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const end_drag = (event: MouseEvent): void => {
|
|
59
|
+
if (!state.is_dragging && state.drag_start) {
|
|
60
|
+
handle_cell_click(event, state.drag_start[0], state.drag_start[1]);
|
|
61
|
+
} else if (state.is_dragging && parent_element) {
|
|
62
|
+
parent_element.focus();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
state.is_dragging = false;
|
|
66
|
+
set_is_dragging(false);
|
|
67
|
+
state.drag_start = null;
|
|
68
|
+
state.mouse_down_pos = null;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
handle_mouse_down: start_drag,
|
|
73
|
+
|
|
74
|
+
handle_mouse_move(event: MouseEvent): void {
|
|
75
|
+
if (!state.drag_start || !state.mouse_down_pos) return;
|
|
76
|
+
|
|
77
|
+
const dx = Math.abs(event.clientX - state.mouse_down_pos.x);
|
|
78
|
+
const dy = Math.abs(event.clientY - state.mouse_down_pos.y);
|
|
79
|
+
|
|
80
|
+
if (!state.is_dragging && (dx > 3 || dy > 3)) {
|
|
81
|
+
state.is_dragging = true;
|
|
82
|
+
set_is_dragging(true);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (state.is_dragging) {
|
|
86
|
+
update_selection(event);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
handle_mouse_up: end_drag
|
|
91
|
+
};
|
|
92
|
+
}
|