@gradio/dataframe 0.17.14 → 0.17.16
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 +27 -0
- package/Dataframe.stories.svelte +11 -10
- package/dist/shared/BooleanCell.svelte +62 -0
- package/dist/shared/BooleanCell.svelte.d.ts +18 -0
- package/dist/shared/EditableCell.svelte +67 -44
- package/dist/shared/Table.svelte +24 -5
- package/dist/shared/VirtualTable.svelte +44 -99
- package/dist/shared/utils/drag_utils.js +4 -0
- package/dist/shared/utils/keyboard_utils.js +1 -1
- package/package.json +7 -6
- package/shared/BooleanCell.svelte +68 -0
- package/shared/EditableCell.svelte +69 -45
- package/shared/Table.svelte +25 -7
- package/shared/VirtualTable.svelte +49 -120
- package/shared/utils/drag_utils.ts +5 -0
- package/shared/utils/keyboard_utils.ts +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @gradio/dataframe
|
|
2
2
|
|
|
3
|
+
## 0.17.16
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- [#11349](https://github.com/gradio-app/gradio/pull/11349) [`bed858b`](https://github.com/gradio-app/gradio/commit/bed858b62b9887e606618bea5fe8a2f212b4a645) - Fix DataFrame Scroll Divergence. Thanks @deckar01!
|
|
8
|
+
- [#11346](https://github.com/gradio-app/gradio/pull/11346) [`3a9a002`](https://github.com/gradio-app/gradio/commit/3a9a0025efd49942303517c5dbd426d41241b7f6) - Hide native Dataframe to screen readers. Thanks @hannahblair!
|
|
9
|
+
|
|
10
|
+
### Dependency updates
|
|
11
|
+
|
|
12
|
+
- @gradio/markdown-code@0.4.3
|
|
13
|
+
- @gradio/statustracker@0.10.12
|
|
14
|
+
- @gradio/button@0.5.3
|
|
15
|
+
- @gradio/checkbox@0.4.23
|
|
16
|
+
|
|
17
|
+
## 0.17.15
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
- [#11237](https://github.com/gradio-app/gradio/pull/11237) [`a6f6b40`](https://github.com/gradio-app/gradio/commit/a6f6b40dda5194fa5bc9926ef67f2a75f503e9a4) - Enhance boolean cell types in `gr.Dataframe`. Thanks @hannahblair!
|
|
22
|
+
|
|
23
|
+
### Dependency updates
|
|
24
|
+
|
|
25
|
+
- @gradio/upload@0.16.7
|
|
26
|
+
- @gradio/checkbox@0.4.23
|
|
27
|
+
- @gradio/client@1.15.2
|
|
28
|
+
- @gradio/button@0.5.3
|
|
29
|
+
|
|
3
30
|
## 0.17.14
|
|
4
31
|
|
|
5
32
|
### Fixes
|
package/Dataframe.stories.svelte
CHANGED
|
@@ -60,11 +60,12 @@
|
|
|
60
60
|
name="Interactive dataframe with label"
|
|
61
61
|
args={{
|
|
62
62
|
values: [
|
|
63
|
-
["Cat", 5],
|
|
64
|
-
["Horse", 3],
|
|
65
|
-
["Snake", 1]
|
|
63
|
+
["Cat", 5, true],
|
|
64
|
+
["Horse", 3, false],
|
|
65
|
+
["Snake", 1, false]
|
|
66
66
|
],
|
|
67
|
-
headers: ["Animal", "Votes"],
|
|
67
|
+
headers: ["Animal", "Votes", "Is Pet"],
|
|
68
|
+
datatype: ["str", "number", "bool"],
|
|
68
69
|
label: "Animals",
|
|
69
70
|
show_label: true,
|
|
70
71
|
col_count: [2, "dynamic"],
|
|
@@ -93,14 +94,14 @@
|
|
|
93
94
|
name="Static dataframe"
|
|
94
95
|
args={{
|
|
95
96
|
values: [
|
|
96
|
-
["Cat", 5],
|
|
97
|
-
["Horse", 3],
|
|
98
|
-
["Snake", 1]
|
|
97
|
+
["Cat", 5, true],
|
|
98
|
+
["Horse", 3, false],
|
|
99
|
+
["Snake", 1, false]
|
|
99
100
|
],
|
|
100
|
-
headers: ["Animal", "Votes"],
|
|
101
|
-
|
|
101
|
+
headers: ["Animal", "Votes", "Is Pet"],
|
|
102
|
+
datatype: ["str", "number", "bool"],
|
|
102
103
|
label: "Animals",
|
|
103
|
-
col_count: [
|
|
104
|
+
col_count: [3, "dynamic"],
|
|
104
105
|
row_count: [3, "dynamic"],
|
|
105
106
|
editable: false
|
|
106
107
|
}}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script>import { BaseCheckbox } from "@gradio/checkbox";
|
|
2
|
+
export let value = false;
|
|
3
|
+
export let editable = true;
|
|
4
|
+
export let on_change;
|
|
5
|
+
$:
|
|
6
|
+
bool_value = typeof value === "string" ? value.toLowerCase() === "true" : !!value;
|
|
7
|
+
function handle_change(event) {
|
|
8
|
+
on_change(event.detail);
|
|
9
|
+
}
|
|
10
|
+
function handle_click(event) {
|
|
11
|
+
event.stopPropagation();
|
|
12
|
+
}
|
|
13
|
+
function handle_keydown(event) {
|
|
14
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
15
|
+
event.stopPropagation();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<div
|
|
21
|
+
class="bool-cell checkbox"
|
|
22
|
+
on:click={handle_click}
|
|
23
|
+
on:keydown={handle_keydown}
|
|
24
|
+
role="button"
|
|
25
|
+
tabindex="-1"
|
|
26
|
+
>
|
|
27
|
+
<BaseCheckbox
|
|
28
|
+
bind:value={bool_value}
|
|
29
|
+
label=""
|
|
30
|
+
interactive={editable}
|
|
31
|
+
on:change={handle_change}
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<style>
|
|
36
|
+
.bool-cell {
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: center;
|
|
40
|
+
width: var(--size-full);
|
|
41
|
+
height: var(--size-full);
|
|
42
|
+
}
|
|
43
|
+
.bool-cell :global(input:disabled) {
|
|
44
|
+
opacity: 0.8;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.bool-cell.checkbox {
|
|
48
|
+
justify-content: center;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.bool-cell :global(label) {
|
|
52
|
+
margin: 0;
|
|
53
|
+
width: 100%;
|
|
54
|
+
display: flex;
|
|
55
|
+
justify-content: center;
|
|
56
|
+
align-items: center;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.bool-cell :global(span) {
|
|
60
|
+
display: none;
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
declare const __propDef: {
|
|
3
|
+
props: {
|
|
4
|
+
value?: (boolean | string) | undefined;
|
|
5
|
+
editable?: boolean | undefined;
|
|
6
|
+
on_change: (value: boolean) => void;
|
|
7
|
+
};
|
|
8
|
+
events: {
|
|
9
|
+
[evt: string]: CustomEvent<any>;
|
|
10
|
+
};
|
|
11
|
+
slots: {};
|
|
12
|
+
};
|
|
13
|
+
export type BooleanCellProps = typeof __propDef.props;
|
|
14
|
+
export type BooleanCellEvents = typeof __propDef.events;
|
|
15
|
+
export type BooleanCellSlots = typeof __propDef.slots;
|
|
16
|
+
export default class BooleanCell extends SvelteComponent<BooleanCellProps, BooleanCellEvents, BooleanCellSlots> {
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script>import { createEventDispatcher } from "svelte";
|
|
2
2
|
import { MarkdownCode } from "@gradio/markdown-code";
|
|
3
3
|
import SelectionButtons from "./icons/SelectionButtons.svelte";
|
|
4
|
+
import BooleanCell from "./BooleanCell.svelte";
|
|
4
5
|
export let edit;
|
|
5
6
|
export let value = "";
|
|
6
7
|
export let display_value = null;
|
|
@@ -21,9 +22,9 @@ export let show_selection_buttons = false;
|
|
|
21
22
|
export let coords;
|
|
22
23
|
export let on_select_column = null;
|
|
23
24
|
export let on_select_row = null;
|
|
25
|
+
export let el;
|
|
24
26
|
const dispatch = createEventDispatcher();
|
|
25
27
|
let is_expanded = false;
|
|
26
|
-
export let el;
|
|
27
28
|
function truncate_text(text, max_length = null, is_image = false) {
|
|
28
29
|
if (is_image)
|
|
29
30
|
return String(text);
|
|
@@ -65,9 +66,22 @@ function handle_click() {
|
|
|
65
66
|
is_expanded = !is_expanded;
|
|
66
67
|
}
|
|
67
68
|
}
|
|
69
|
+
function handle_bool_change(new_value) {
|
|
70
|
+
value = new_value.toString();
|
|
71
|
+
dispatch("blur", {
|
|
72
|
+
blur_event: {
|
|
73
|
+
target: {
|
|
74
|
+
type: "checkbox",
|
|
75
|
+
checked: new_value,
|
|
76
|
+
value: new_value.toString()
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
coords
|
|
80
|
+
});
|
|
81
|
+
}
|
|
68
82
|
</script>
|
|
69
83
|
|
|
70
|
-
{#if edit}
|
|
84
|
+
{#if edit && datatype !== "bool"}
|
|
71
85
|
<input
|
|
72
86
|
readonly={is_static}
|
|
73
87
|
aria-readonly={is_static}
|
|
@@ -86,48 +100,57 @@ function handle_click() {
|
|
|
86
100
|
/>
|
|
87
101
|
{/if}
|
|
88
102
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
{
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
103
|
+
{#if datatype === "bool"}
|
|
104
|
+
<BooleanCell
|
|
105
|
+
value={String(display_content)}
|
|
106
|
+
{editable}
|
|
107
|
+
on_change={handle_bool_change}
|
|
108
|
+
/>
|
|
109
|
+
{:else}
|
|
110
|
+
<span
|
|
111
|
+
class:dragging={is_dragging}
|
|
112
|
+
on:click={handle_click}
|
|
113
|
+
on:keydown={handle_keydown}
|
|
114
|
+
tabindex="0"
|
|
115
|
+
role="button"
|
|
116
|
+
class:edit
|
|
117
|
+
class:expanded={is_expanded}
|
|
118
|
+
class:multiline={header}
|
|
119
|
+
on:focus|preventDefault
|
|
120
|
+
style={styling}
|
|
121
|
+
data-editable={editable}
|
|
122
|
+
data-max-chars={max_chars}
|
|
123
|
+
data-expanded={is_expanded}
|
|
124
|
+
placeholder=" "
|
|
125
|
+
class:text={datatype === "str"}
|
|
126
|
+
class:wrap={wrap_text}
|
|
127
|
+
>
|
|
128
|
+
{#if datatype === "image" && components.image}
|
|
129
|
+
<svelte:component
|
|
130
|
+
this={components.image}
|
|
131
|
+
value={{ url: display_text }}
|
|
132
|
+
show_label={false}
|
|
133
|
+
label="cell-image"
|
|
134
|
+
show_download_button={false}
|
|
135
|
+
{i18n}
|
|
136
|
+
gradio={{ dispatch: () => {} }}
|
|
137
|
+
/>
|
|
138
|
+
{:else if datatype === "html"}
|
|
139
|
+
{@html display_text}
|
|
140
|
+
{:else if datatype === "markdown"}
|
|
141
|
+
<MarkdownCode
|
|
142
|
+
message={display_text.toLocaleString()}
|
|
143
|
+
{latex_delimiters}
|
|
144
|
+
{line_breaks}
|
|
145
|
+
chatbot={false}
|
|
146
|
+
{root}
|
|
147
|
+
/>
|
|
148
|
+
{:else}
|
|
149
|
+
{display_text}
|
|
150
|
+
{/if}
|
|
151
|
+
</span>
|
|
152
|
+
{/if}
|
|
153
|
+
|
|
131
154
|
{#if show_selection_buttons && coords && on_select_column && on_select_row}
|
|
132
155
|
<SelectionButtons
|
|
133
156
|
position="column"
|
package/dist/shared/Table.svelte
CHANGED
|
@@ -103,7 +103,7 @@ onMount(() => {
|
|
|
103
103
|
const observer = new IntersectionObserver((entries) => {
|
|
104
104
|
entries.forEach((entry) => {
|
|
105
105
|
if (entry.isIntersecting && !is_visible) {
|
|
106
|
-
|
|
106
|
+
width_calculated = false;
|
|
107
107
|
}
|
|
108
108
|
is_visible = entry.isIntersecting;
|
|
109
109
|
});
|
|
@@ -111,10 +111,17 @@ onMount(() => {
|
|
|
111
111
|
observer.observe(parent);
|
|
112
112
|
document.addEventListener("click", handle_click_outside);
|
|
113
113
|
window.addEventListener("resize", handle_resize);
|
|
114
|
+
const global_mouse_up = (event) => {
|
|
115
|
+
if (is_dragging || drag_start) {
|
|
116
|
+
handle_mouse_up(event);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
document.addEventListener("mouseup", global_mouse_up);
|
|
114
120
|
return () => {
|
|
115
121
|
observer.disconnect();
|
|
116
122
|
document.removeEventListener("click", handle_click_outside);
|
|
117
123
|
window.removeEventListener("resize", handle_resize);
|
|
124
|
+
document.removeEventListener("mouseup", global_mouse_up);
|
|
118
125
|
};
|
|
119
126
|
});
|
|
120
127
|
$: {
|
|
@@ -170,6 +177,7 @@ $:
|
|
|
170
177
|
}
|
|
171
178
|
last_width_data_length = 0;
|
|
172
179
|
last_width_column_count = 0;
|
|
180
|
+
width_calculated = false;
|
|
173
181
|
}
|
|
174
182
|
}
|
|
175
183
|
const is_reset = values.length === 0 || values.length === 1 && values[0].length === 0;
|
|
@@ -193,8 +201,8 @@ $:
|
|
|
193
201
|
if ($df_state.current_search_query) {
|
|
194
202
|
df_actions.handle_search(null);
|
|
195
203
|
}
|
|
196
|
-
if (parent && cells.length > 0) {
|
|
197
|
-
|
|
204
|
+
if (parent && cells.length > 0 && (is_reset || is_different_structure)) {
|
|
205
|
+
width_calculated = false;
|
|
198
206
|
}
|
|
199
207
|
}
|
|
200
208
|
$:
|
|
@@ -328,8 +336,18 @@ function handle_click_outside(event) {
|
|
|
328
336
|
}
|
|
329
337
|
$:
|
|
330
338
|
max = get_max(data);
|
|
339
|
+
let width_calc_timeout;
|
|
331
340
|
$:
|
|
332
|
-
cells[0] && cells[0]?.clientWidth
|
|
341
|
+
if (cells[0] && cells[0]?.clientWidth) {
|
|
342
|
+
clearTimeout(width_calc_timeout);
|
|
343
|
+
width_calc_timeout = setTimeout(() => set_cell_widths(), 100);
|
|
344
|
+
}
|
|
345
|
+
let width_calculated = false;
|
|
346
|
+
$:
|
|
347
|
+
if (cells[0] && !width_calculated) {
|
|
348
|
+
set_cell_widths();
|
|
349
|
+
width_calculated = true;
|
|
350
|
+
}
|
|
333
351
|
let cells = [];
|
|
334
352
|
let parent;
|
|
335
353
|
let table;
|
|
@@ -512,6 +530,7 @@ function handle_resize() {
|
|
|
512
530
|
selected_cells = [];
|
|
513
531
|
selected = false;
|
|
514
532
|
editing = false;
|
|
533
|
+
width_calculated = false;
|
|
515
534
|
set_cell_widths();
|
|
516
535
|
}
|
|
517
536
|
function add_row_at(index, position) {
|
|
@@ -615,7 +634,7 @@ function get_cell_display_value(row, col) {
|
|
|
615
634
|
role="grid"
|
|
616
635
|
tabindex="0"
|
|
617
636
|
>
|
|
618
|
-
<table bind:this={table}>
|
|
637
|
+
<table bind:this={table} aria-hidden="true">
|
|
619
638
|
{#if label && label.length !== 0}
|
|
620
639
|
<caption class="sr-only">{label}</caption>
|
|
621
640
|
{/if}
|
|
@@ -28,53 +28,64 @@ $:
|
|
|
28
28
|
viewport_height = viewport_box?.height || 200;
|
|
29
29
|
const is_browser = typeof window !== "undefined";
|
|
30
30
|
const raf = is_browser ? window.requestAnimationFrame : (cb) => cb();
|
|
31
|
-
$:
|
|
32
|
-
mounted &&
|
|
33
|
-
|
|
34
|
-
async function refresh_height_map(_items) {
|
|
35
|
-
if (viewport_height === 0) {
|
|
36
|
-
return;
|
|
31
|
+
$: {
|
|
32
|
+
if (mounted && viewport_height && viewport.offsetParent) {
|
|
33
|
+
sortedItems, raf(refresh_height_map);
|
|
37
34
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
}
|
|
36
|
+
async function refresh_height_map() {
|
|
37
|
+
if (sortedItems.length < start) {
|
|
38
|
+
await scroll_to_index(sortedItems.length - 1, { behavior: "auto" });
|
|
39
|
+
}
|
|
40
|
+
const scrollTop = Math.max(0, viewport.scrollTop);
|
|
41
|
+
show_scroll_button = scrollTop > 100;
|
|
41
42
|
table_scrollbar_width = viewport.offsetWidth - viewport.clientWidth;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
_h = average_height;
|
|
43
|
+
for (let v = 0; v < rows.length; v += 1) {
|
|
44
|
+
height_map[start + v] = rows[v].getBoundingClientRect().height;
|
|
45
|
+
}
|
|
46
|
+
let i = 0;
|
|
47
|
+
let y = head_height;
|
|
48
|
+
while (i < sortedItems.length) {
|
|
49
|
+
const row_height = height_map[i] || average_height;
|
|
50
|
+
if (y + row_height > scrollTop - max_height) {
|
|
51
|
+
start = i;
|
|
52
|
+
top = y - head_height;
|
|
53
|
+
break;
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
y += row_height;
|
|
56
|
+
i += 1;
|
|
57
|
+
}
|
|
58
|
+
let content_height = head_height;
|
|
59
|
+
while (i < sortedItems.length) {
|
|
60
|
+
const row_height = height_map[i] || average_height;
|
|
56
61
|
content_height += row_height;
|
|
57
62
|
i += 1;
|
|
63
|
+
if (content_height - head_height > 3 * max_height) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
58
66
|
}
|
|
59
67
|
end = i;
|
|
60
|
-
const remaining =
|
|
68
|
+
const remaining = sortedItems.length - end;
|
|
61
69
|
const scrollbar_height = viewport.offsetHeight - viewport.clientHeight;
|
|
62
70
|
if (scrollbar_height > 0) {
|
|
63
71
|
content_height += scrollbar_height;
|
|
64
72
|
}
|
|
65
73
|
let filtered_height_map = height_map.filter((v) => typeof v === "number");
|
|
66
|
-
average_height = filtered_height_map.reduce((a, b) => a + b, 0) / filtered_height_map.length;
|
|
74
|
+
average_height = filtered_height_map.reduce((a, b) => a + b, 0) / filtered_height_map.length || 30;
|
|
67
75
|
bottom = remaining * average_height;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
if (!isFinite(bottom)) {
|
|
77
|
+
bottom = 2e5;
|
|
78
|
+
}
|
|
79
|
+
height_map.length = sortedItems.length;
|
|
80
|
+
while (i < sortedItems.length) {
|
|
81
|
+
i += 1;
|
|
82
|
+
height_map[i] = average_height;
|
|
83
|
+
}
|
|
84
|
+
if (max_height && content_height > max_height) {
|
|
75
85
|
actual_height = max_height;
|
|
86
|
+
} else {
|
|
87
|
+
actual_height = content_height;
|
|
76
88
|
}
|
|
77
|
-
await tick();
|
|
78
89
|
}
|
|
79
90
|
$:
|
|
80
91
|
scroll_and_render(selected);
|
|
@@ -112,71 +123,6 @@ function is_in_view(n) {
|
|
|
112
123
|
}
|
|
113
124
|
return true;
|
|
114
125
|
}
|
|
115
|
-
function get_computed_px_amount(elem, property) {
|
|
116
|
-
if (!elem) {
|
|
117
|
-
return 0;
|
|
118
|
-
}
|
|
119
|
-
const compStyle = getComputedStyle(elem);
|
|
120
|
-
let x = parseInt(compStyle.getPropertyValue(property));
|
|
121
|
-
return x;
|
|
122
|
-
}
|
|
123
|
-
async function handle_scroll(e) {
|
|
124
|
-
const scroll_top = viewport.scrollTop;
|
|
125
|
-
show_scroll_button = scroll_top > 100;
|
|
126
|
-
if (show_scroll_button) {
|
|
127
|
-
dispatch("scroll_top", scroll_top);
|
|
128
|
-
}
|
|
129
|
-
rows = contents.children;
|
|
130
|
-
const is_start_overflow = sortedItems.length < start;
|
|
131
|
-
const row_top_border = get_computed_px_amount(rows[1], "border-top-width");
|
|
132
|
-
const actual_border_collapsed_width = 0;
|
|
133
|
-
if (is_start_overflow) {
|
|
134
|
-
await scroll_to_index(sortedItems.length - 1, { behavior: "auto" });
|
|
135
|
-
}
|
|
136
|
-
let new_start = 0;
|
|
137
|
-
for (let v = 0; v < rows.length; v += 1) {
|
|
138
|
-
height_map[start + v] = rows[v].getBoundingClientRect().height;
|
|
139
|
-
}
|
|
140
|
-
let i = 0;
|
|
141
|
-
let y = head_height + row_top_border / 2;
|
|
142
|
-
let row_heights = [];
|
|
143
|
-
while (i < sortedItems.length) {
|
|
144
|
-
const row_height = height_map[i] || average_height;
|
|
145
|
-
row_heights[i] = row_height;
|
|
146
|
-
if (y + row_height + actual_border_collapsed_width > scroll_top) {
|
|
147
|
-
new_start = i;
|
|
148
|
-
top = y - (head_height + row_top_border / 2);
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
y += row_height;
|
|
152
|
-
i += 1;
|
|
153
|
-
}
|
|
154
|
-
new_start = Math.max(0, new_start);
|
|
155
|
-
while (i < sortedItems.length) {
|
|
156
|
-
const row_height = height_map[i] || average_height;
|
|
157
|
-
y += row_height;
|
|
158
|
-
i += 1;
|
|
159
|
-
if (y > scroll_top + viewport_height) {
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
start = new_start;
|
|
164
|
-
end = i;
|
|
165
|
-
const remaining = sortedItems.length - end;
|
|
166
|
-
if (end === 0) {
|
|
167
|
-
end = 10;
|
|
168
|
-
}
|
|
169
|
-
average_height = (y - head_height) / end;
|
|
170
|
-
let remaining_height = remaining * average_height;
|
|
171
|
-
while (i < sortedItems.length) {
|
|
172
|
-
i += 1;
|
|
173
|
-
height_map[i] = average_height;
|
|
174
|
-
}
|
|
175
|
-
bottom = remaining_height;
|
|
176
|
-
if (!isFinite(bottom)) {
|
|
177
|
-
bottom = 2e5;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
126
|
export async function scroll_to_index(index, opts, align_end = false) {
|
|
181
127
|
await tick();
|
|
182
128
|
const _itemHeight = average_height;
|
|
@@ -206,7 +152,6 @@ $:
|
|
|
206
152
|
onMount(() => {
|
|
207
153
|
rows = contents.children;
|
|
208
154
|
mounted = true;
|
|
209
|
-
refresh_height_map(items);
|
|
210
155
|
});
|
|
211
156
|
</script>
|
|
212
157
|
|
|
@@ -217,7 +162,7 @@ onMount(() => {
|
|
|
217
162
|
class:disable-scroll={disable_scroll}
|
|
218
163
|
bind:this={viewport}
|
|
219
164
|
bind:contentRect={viewport_box}
|
|
220
|
-
on:scroll={
|
|
165
|
+
on:scroll={refresh_height_map}
|
|
221
166
|
style="height: {height}; --bw-svt-p-top: {top}px; --bw-svt-p-bottom: {bottom}px; --bw-svt-head-height: {head_height}px; --bw-svt-foot-height: {foot_height}px; --bw-svt-avg-row-height: {average_height}px; --max-height: {max_height}px"
|
|
222
167
|
>
|
|
223
168
|
<thead class="thead" bind:offsetHeight={head_height}>
|
|
@@ -43,6 +43,10 @@ export function create_drag_handlers(state, set_is_dragging, set_selected_cells,
|
|
|
43
43
|
handle_mouse_move(event) {
|
|
44
44
|
if (!state.drag_start || !state.mouse_down_pos)
|
|
45
45
|
return;
|
|
46
|
+
if (!(event.buttons & 1)) {
|
|
47
|
+
end_drag(event);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
46
50
|
const dx = Math.abs(event.clientX - state.mouse_down_pos.x);
|
|
47
51
|
const dy = Math.abs(event.clientY - state.mouse_down_pos.y);
|
|
48
52
|
if (!state.is_dragging && (dx > 3 || dy > 3)) {
|
|
@@ -23,7 +23,7 @@ export async function handle_cell_blur(event, ctx, coords) {
|
|
|
23
23
|
const input_el = event.target;
|
|
24
24
|
if (!input_el || input_el.value === undefined)
|
|
25
25
|
return;
|
|
26
|
-
await save_cell_value(input_el.value, ctx, coords[0], coords[1]);
|
|
26
|
+
await save_cell_value(input_el.type === "checkbox" ? String(input_el.checked) : input_el.value, ctx, coords[0], coords[1]);
|
|
27
27
|
}
|
|
28
28
|
function handle_header_navigation(event, ctx) {
|
|
29
29
|
const state = get(ctx.state);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/dataframe",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.16",
|
|
4
4
|
"description": "Gradio UI packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
@@ -18,12 +18,13 @@
|
|
|
18
18
|
"katex": "^0.16.7",
|
|
19
19
|
"marked": "^12.0.0",
|
|
20
20
|
"@gradio/atoms": "^0.16.1",
|
|
21
|
-
"@gradio/button": "^0.5.
|
|
22
|
-
"@gradio/client": "^1.15.1",
|
|
21
|
+
"@gradio/button": "^0.5.3",
|
|
23
22
|
"@gradio/icons": "^0.12.0",
|
|
24
|
-
"@gradio/markdown-code": "^0.4.3",
|
|
25
|
-
"@gradio/upload": "^0.16.6",
|
|
26
23
|
"@gradio/statustracker": "^0.10.12",
|
|
24
|
+
"@gradio/checkbox": "^0.4.23",
|
|
25
|
+
"@gradio/upload": "^0.16.7",
|
|
26
|
+
"@gradio/markdown-code": "^0.4.3",
|
|
27
|
+
"@gradio/client": "^1.15.2",
|
|
27
28
|
"@gradio/utils": "^0.10.2"
|
|
28
29
|
},
|
|
29
30
|
"exports": {
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
"svelte": "^4.0.0"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
|
-
"@gradio/preview": "^0.13.
|
|
47
|
+
"@gradio/preview": "^0.13.2"
|
|
47
48
|
},
|
|
48
49
|
"repository": {
|
|
49
50
|
"type": "git",
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { BaseCheckbox } from "@gradio/checkbox";
|
|
3
|
+
|
|
4
|
+
export let value: boolean | string = false;
|
|
5
|
+
export let editable = true;
|
|
6
|
+
export let on_change: (value: boolean) => void;
|
|
7
|
+
|
|
8
|
+
$: bool_value =
|
|
9
|
+
typeof value === "string" ? value.toLowerCase() === "true" : !!value;
|
|
10
|
+
|
|
11
|
+
function handle_change(event: CustomEvent<boolean>): void {
|
|
12
|
+
on_change(event.detail);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function handle_click(event: MouseEvent): void {
|
|
16
|
+
event.stopPropagation();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function handle_keydown(event: KeyboardEvent): void {
|
|
20
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
21
|
+
event.stopPropagation();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<div
|
|
27
|
+
class="bool-cell checkbox"
|
|
28
|
+
on:click={handle_click}
|
|
29
|
+
on:keydown={handle_keydown}
|
|
30
|
+
role="button"
|
|
31
|
+
tabindex="-1"
|
|
32
|
+
>
|
|
33
|
+
<BaseCheckbox
|
|
34
|
+
bind:value={bool_value}
|
|
35
|
+
label=""
|
|
36
|
+
interactive={editable}
|
|
37
|
+
on:change={handle_change}
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<style>
|
|
42
|
+
.bool-cell {
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: center;
|
|
46
|
+
width: var(--size-full);
|
|
47
|
+
height: var(--size-full);
|
|
48
|
+
}
|
|
49
|
+
.bool-cell :global(input:disabled) {
|
|
50
|
+
opacity: 0.8;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.bool-cell.checkbox {
|
|
54
|
+
justify-content: center;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.bool-cell :global(label) {
|
|
58
|
+
margin: 0;
|
|
59
|
+
width: 100%;
|
|
60
|
+
display: flex;
|
|
61
|
+
justify-content: center;
|
|
62
|
+
align-items: center;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.bool-cell :global(span) {
|
|
66
|
+
display: none;
|
|
67
|
+
}
|
|
68
|
+
</style>
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { MarkdownCode } from "@gradio/markdown-code";
|
|
4
4
|
import type { I18nFormatter } from "@gradio/utils";
|
|
5
5
|
import SelectionButtons from "./icons/SelectionButtons.svelte";
|
|
6
|
+
import BooleanCell from "./BooleanCell.svelte";
|
|
7
|
+
|
|
6
8
|
export let edit: boolean;
|
|
7
9
|
export let value: string | number = "";
|
|
8
10
|
export let display_value: string | null = null;
|
|
@@ -35,6 +37,7 @@
|
|
|
35
37
|
export let coords: [number, number];
|
|
36
38
|
export let on_select_column: ((col: number) => void) | null = null;
|
|
37
39
|
export let on_select_row: ((row: number) => void) | null = null;
|
|
40
|
+
export let el: HTMLInputElement | null;
|
|
38
41
|
|
|
39
42
|
const dispatch = createEventDispatcher<{
|
|
40
43
|
blur: { blur_event: FocusEvent; coords: [number, number] };
|
|
@@ -43,8 +46,6 @@
|
|
|
43
46
|
|
|
44
47
|
let is_expanded = false;
|
|
45
48
|
|
|
46
|
-
export let el: HTMLInputElement | null;
|
|
47
|
-
|
|
48
49
|
function truncate_text(
|
|
49
50
|
text: string | number,
|
|
50
51
|
max_length: number | null = null,
|
|
@@ -99,9 +100,23 @@
|
|
|
99
100
|
is_expanded = !is_expanded;
|
|
100
101
|
}
|
|
101
102
|
}
|
|
103
|
+
|
|
104
|
+
function handle_bool_change(new_value: boolean): void {
|
|
105
|
+
value = new_value.toString();
|
|
106
|
+
dispatch("blur", {
|
|
107
|
+
blur_event: {
|
|
108
|
+
target: {
|
|
109
|
+
type: "checkbox",
|
|
110
|
+
checked: new_value,
|
|
111
|
+
value: new_value.toString()
|
|
112
|
+
}
|
|
113
|
+
} as unknown as FocusEvent,
|
|
114
|
+
coords: coords
|
|
115
|
+
});
|
|
116
|
+
}
|
|
102
117
|
</script>
|
|
103
118
|
|
|
104
|
-
{#if edit}
|
|
119
|
+
{#if edit && datatype !== "bool"}
|
|
105
120
|
<input
|
|
106
121
|
readonly={is_static}
|
|
107
122
|
aria-readonly={is_static}
|
|
@@ -120,48 +135,57 @@
|
|
|
120
135
|
/>
|
|
121
136
|
{/if}
|
|
122
137
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
{
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
138
|
+
{#if datatype === "bool"}
|
|
139
|
+
<BooleanCell
|
|
140
|
+
value={String(display_content)}
|
|
141
|
+
{editable}
|
|
142
|
+
on_change={handle_bool_change}
|
|
143
|
+
/>
|
|
144
|
+
{:else}
|
|
145
|
+
<span
|
|
146
|
+
class:dragging={is_dragging}
|
|
147
|
+
on:click={handle_click}
|
|
148
|
+
on:keydown={handle_keydown}
|
|
149
|
+
tabindex="0"
|
|
150
|
+
role="button"
|
|
151
|
+
class:edit
|
|
152
|
+
class:expanded={is_expanded}
|
|
153
|
+
class:multiline={header}
|
|
154
|
+
on:focus|preventDefault
|
|
155
|
+
style={styling}
|
|
156
|
+
data-editable={editable}
|
|
157
|
+
data-max-chars={max_chars}
|
|
158
|
+
data-expanded={is_expanded}
|
|
159
|
+
placeholder=" "
|
|
160
|
+
class:text={datatype === "str"}
|
|
161
|
+
class:wrap={wrap_text}
|
|
162
|
+
>
|
|
163
|
+
{#if datatype === "image" && components.image}
|
|
164
|
+
<svelte:component
|
|
165
|
+
this={components.image}
|
|
166
|
+
value={{ url: display_text }}
|
|
167
|
+
show_label={false}
|
|
168
|
+
label="cell-image"
|
|
169
|
+
show_download_button={false}
|
|
170
|
+
{i18n}
|
|
171
|
+
gradio={{ dispatch: () => {} }}
|
|
172
|
+
/>
|
|
173
|
+
{:else if datatype === "html"}
|
|
174
|
+
{@html display_text}
|
|
175
|
+
{:else if datatype === "markdown"}
|
|
176
|
+
<MarkdownCode
|
|
177
|
+
message={display_text.toLocaleString()}
|
|
178
|
+
{latex_delimiters}
|
|
179
|
+
{line_breaks}
|
|
180
|
+
chatbot={false}
|
|
181
|
+
{root}
|
|
182
|
+
/>
|
|
183
|
+
{:else}
|
|
184
|
+
{display_text}
|
|
185
|
+
{/if}
|
|
186
|
+
</span>
|
|
187
|
+
{/if}
|
|
188
|
+
|
|
165
189
|
{#if show_selection_buttons && coords && on_select_column && on_select_row}
|
|
166
190
|
<SelectionButtons
|
|
167
191
|
position="column"
|
package/shared/Table.svelte
CHANGED
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
const observer = new IntersectionObserver((entries) => {
|
|
121
121
|
entries.forEach((entry) => {
|
|
122
122
|
if (entry.isIntersecting && !is_visible) {
|
|
123
|
-
|
|
123
|
+
width_calculated = false;
|
|
124
124
|
}
|
|
125
125
|
is_visible = entry.isIntersecting;
|
|
126
126
|
});
|
|
@@ -129,10 +129,18 @@
|
|
|
129
129
|
document.addEventListener("click", handle_click_outside);
|
|
130
130
|
window.addEventListener("resize", handle_resize);
|
|
131
131
|
|
|
132
|
+
const global_mouse_up = (event: MouseEvent): void => {
|
|
133
|
+
if (is_dragging || drag_start) {
|
|
134
|
+
handle_mouse_up(event);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
document.addEventListener("mouseup", global_mouse_up);
|
|
138
|
+
|
|
132
139
|
return () => {
|
|
133
140
|
observer.disconnect();
|
|
134
141
|
document.removeEventListener("click", handle_click_outside);
|
|
135
142
|
window.removeEventListener("resize", handle_resize);
|
|
143
|
+
document.removeEventListener("mouseup", global_mouse_up);
|
|
136
144
|
};
|
|
137
145
|
});
|
|
138
146
|
|
|
@@ -223,6 +231,7 @@
|
|
|
223
231
|
}
|
|
224
232
|
last_width_data_length = 0;
|
|
225
233
|
last_width_column_count = 0;
|
|
234
|
+
width_calculated = false;
|
|
226
235
|
}
|
|
227
236
|
}
|
|
228
237
|
|
|
@@ -256,8 +265,8 @@
|
|
|
256
265
|
df_actions.handle_search(null);
|
|
257
266
|
}
|
|
258
267
|
|
|
259
|
-
if (parent && cells.length > 0) {
|
|
260
|
-
|
|
268
|
+
if (parent && cells.length > 0 && (is_reset || is_different_structure)) {
|
|
269
|
+
width_calculated = false;
|
|
261
270
|
}
|
|
262
271
|
}
|
|
263
272
|
|
|
@@ -421,9 +430,17 @@
|
|
|
421
430
|
|
|
422
431
|
$: max = get_max(data);
|
|
423
432
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
433
|
+
let width_calc_timeout: ReturnType<typeof setTimeout>;
|
|
434
|
+
$: if (cells[0] && cells[0]?.clientWidth) {
|
|
435
|
+
clearTimeout(width_calc_timeout);
|
|
436
|
+
width_calc_timeout = setTimeout(() => set_cell_widths(), 100);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
let width_calculated = false;
|
|
440
|
+
$: if (cells[0] && !width_calculated) {
|
|
441
|
+
set_cell_widths();
|
|
442
|
+
width_calculated = true;
|
|
443
|
+
}
|
|
427
444
|
let cells: HTMLTableCellElement[] = [];
|
|
428
445
|
let parent: HTMLDivElement;
|
|
429
446
|
let table: HTMLTableElement;
|
|
@@ -649,6 +666,7 @@
|
|
|
649
666
|
selected_cells = [];
|
|
650
667
|
selected = false;
|
|
651
668
|
editing = false;
|
|
669
|
+
width_calculated = false;
|
|
652
670
|
set_cell_widths();
|
|
653
671
|
}
|
|
654
672
|
|
|
@@ -762,7 +780,7 @@
|
|
|
762
780
|
role="grid"
|
|
763
781
|
tabindex="0"
|
|
764
782
|
>
|
|
765
|
-
<table bind:this={table}>
|
|
783
|
+
<table bind:this={table} aria-hidden="true">
|
|
766
784
|
{#if label && label.length !== 0}
|
|
767
785
|
<caption class="sr-only">{label}</caption>
|
|
768
786
|
{/if}
|
|
@@ -40,43 +40,53 @@
|
|
|
40
40
|
? window.requestAnimationFrame
|
|
41
41
|
: (cb: (...args: any[]) => void) => cb();
|
|
42
42
|
|
|
43
|
-
$:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
async function refresh_height_map(_items: typeof items): Promise<void> {
|
|
47
|
-
if (viewport_height === 0) {
|
|
48
|
-
return;
|
|
43
|
+
$: {
|
|
44
|
+
if (mounted && viewport_height && viewport.offsetParent) {
|
|
45
|
+
sortedItems, raf(refresh_height_map);
|
|
49
46
|
}
|
|
47
|
+
}
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
async function refresh_height_map(): Promise<void> {
|
|
50
|
+
if (sortedItems.length < start) {
|
|
51
|
+
await scroll_to_index(sortedItems.length - 1, { behavior: "auto" });
|
|
52
|
+
}
|
|
55
53
|
|
|
56
|
-
const
|
|
54
|
+
const scrollTop = Math.max(0, viewport.scrollTop);
|
|
55
|
+
show_scroll_button = scrollTop > 100;
|
|
57
56
|
table_scrollbar_width = viewport.offsetWidth - viewport.clientWidth;
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
let
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
58
|
+
// acquire height map for currently visible rows
|
|
59
|
+
for (let v = 0; v < rows.length; v += 1) {
|
|
60
|
+
height_map[start + v] = rows[v].getBoundingClientRect().height;
|
|
61
|
+
}
|
|
62
|
+
let i = 0;
|
|
63
|
+
let y = head_height;
|
|
64
|
+
// loop items to find new start
|
|
65
|
+
while (i < sortedItems.length) {
|
|
66
|
+
const row_height = height_map[i] || average_height;
|
|
67
|
+
// keep a page of rows buffered above
|
|
68
|
+
if (y + row_height > scrollTop - max_height) {
|
|
69
|
+
start = i;
|
|
70
|
+
top = y - head_height;
|
|
71
|
+
break;
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
y += row_height;
|
|
74
|
+
i += 1;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let content_height = head_height;
|
|
78
|
+
while (i < sortedItems.length) {
|
|
79
|
+
const row_height = height_map[i] || average_height;
|
|
74
80
|
content_height += row_height;
|
|
75
81
|
i += 1;
|
|
82
|
+
// keep a page of rows buffered below
|
|
83
|
+
if (content_height - head_height > 3 * max_height) {
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
76
86
|
}
|
|
77
87
|
|
|
78
88
|
end = i;
|
|
79
|
-
const remaining =
|
|
89
|
+
const remaining = sortedItems.length - end;
|
|
80
90
|
|
|
81
91
|
const scrollbar_height = viewport.offsetHeight - viewport.clientHeight;
|
|
82
92
|
if (scrollbar_height > 0) {
|
|
@@ -86,20 +96,22 @@
|
|
|
86
96
|
let filtered_height_map = height_map.filter((v) => typeof v === "number");
|
|
87
97
|
average_height =
|
|
88
98
|
filtered_height_map.reduce((a, b) => a + b, 0) /
|
|
89
|
-
|
|
99
|
+
filtered_height_map.length || 30;
|
|
90
100
|
|
|
91
101
|
bottom = remaining * average_height;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
if (!isFinite(bottom)) {
|
|
103
|
+
bottom = 200000;
|
|
104
|
+
}
|
|
105
|
+
height_map.length = sortedItems.length;
|
|
106
|
+
while (i < sortedItems.length) {
|
|
107
|
+
i += 1;
|
|
108
|
+
height_map[i] = average_height;
|
|
109
|
+
}
|
|
110
|
+
if (max_height && content_height > max_height) {
|
|
99
111
|
actual_height = max_height;
|
|
112
|
+
} else {
|
|
113
|
+
actual_height = content_height;
|
|
100
114
|
}
|
|
101
|
-
|
|
102
|
-
await tick();
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
$: scroll_and_render(selected);
|
|
@@ -144,88 +156,6 @@
|
|
|
144
156
|
return true;
|
|
145
157
|
}
|
|
146
158
|
|
|
147
|
-
function get_computed_px_amount(elem: HTMLElement, property: string): number {
|
|
148
|
-
if (!elem) {
|
|
149
|
-
return 0;
|
|
150
|
-
}
|
|
151
|
-
const compStyle = getComputedStyle(elem);
|
|
152
|
-
|
|
153
|
-
let x = parseInt(compStyle.getPropertyValue(property));
|
|
154
|
-
return x;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async function handle_scroll(e: Event): Promise<void> {
|
|
158
|
-
const scroll_top = viewport.scrollTop;
|
|
159
|
-
|
|
160
|
-
show_scroll_button = scroll_top > 100;
|
|
161
|
-
|
|
162
|
-
if (show_scroll_button) {
|
|
163
|
-
dispatch("scroll_top", scroll_top);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
rows = contents.children as HTMLCollectionOf<HTMLTableRowElement>;
|
|
167
|
-
const is_start_overflow = sortedItems.length < start;
|
|
168
|
-
|
|
169
|
-
const row_top_border = get_computed_px_amount(rows[1], "border-top-width");
|
|
170
|
-
|
|
171
|
-
const actual_border_collapsed_width = 0;
|
|
172
|
-
|
|
173
|
-
if (is_start_overflow) {
|
|
174
|
-
await scroll_to_index(sortedItems.length - 1, { behavior: "auto" });
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
let new_start = 0;
|
|
178
|
-
// acquire height map for currently visible rows
|
|
179
|
-
for (let v = 0; v < rows.length; v += 1) {
|
|
180
|
-
height_map[start + v] = rows[v].getBoundingClientRect().height;
|
|
181
|
-
}
|
|
182
|
-
let i = 0;
|
|
183
|
-
// start from top: thead, with its borders, plus the first border to afterwards neglect
|
|
184
|
-
let y = head_height + row_top_border / 2;
|
|
185
|
-
let row_heights = [];
|
|
186
|
-
// loop items to find new start
|
|
187
|
-
while (i < sortedItems.length) {
|
|
188
|
-
const row_height = height_map[i] || average_height;
|
|
189
|
-
row_heights[i] = row_height;
|
|
190
|
-
// we only want to jump if the full (incl. border) row is away
|
|
191
|
-
if (y + row_height + actual_border_collapsed_width > scroll_top) {
|
|
192
|
-
// this is the last index still inside the viewport
|
|
193
|
-
new_start = i;
|
|
194
|
-
top = y - (head_height + row_top_border / 2);
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
y += row_height;
|
|
198
|
-
i += 1;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
new_start = Math.max(0, new_start);
|
|
202
|
-
while (i < sortedItems.length) {
|
|
203
|
-
const row_height = height_map[i] || average_height;
|
|
204
|
-
y += row_height;
|
|
205
|
-
i += 1;
|
|
206
|
-
if (y > scroll_top + viewport_height) {
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
start = new_start;
|
|
211
|
-
end = i;
|
|
212
|
-
const remaining = sortedItems.length - end;
|
|
213
|
-
if (end === 0) {
|
|
214
|
-
end = 10;
|
|
215
|
-
}
|
|
216
|
-
average_height = (y - head_height) / end;
|
|
217
|
-
let remaining_height = remaining * average_height; // 0
|
|
218
|
-
// compute height map for remaining items
|
|
219
|
-
while (i < sortedItems.length) {
|
|
220
|
-
i += 1;
|
|
221
|
-
height_map[i] = average_height;
|
|
222
|
-
}
|
|
223
|
-
bottom = remaining_height;
|
|
224
|
-
if (!isFinite(bottom)) {
|
|
225
|
-
bottom = 200000;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
159
|
export async function scroll_to_index(
|
|
230
160
|
index: number,
|
|
231
161
|
opts: ScrollToOptions,
|
|
@@ -269,7 +199,6 @@
|
|
|
269
199
|
onMount(() => {
|
|
270
200
|
rows = contents.children as HTMLCollectionOf<HTMLTableRowElement>;
|
|
271
201
|
mounted = true;
|
|
272
|
-
refresh_height_map(items);
|
|
273
202
|
});
|
|
274
203
|
</script>
|
|
275
204
|
|
|
@@ -280,7 +209,7 @@
|
|
|
280
209
|
class:disable-scroll={disable_scroll}
|
|
281
210
|
bind:this={viewport}
|
|
282
211
|
bind:contentRect={viewport_box}
|
|
283
|
-
on:scroll={
|
|
212
|
+
on:scroll={refresh_height_map}
|
|
284
213
|
style="height: {height}; --bw-svt-p-top: {top}px; --bw-svt-p-bottom: {bottom}px; --bw-svt-head-height: {head_height}px; --bw-svt-foot-height: {foot_height}px; --bw-svt-avg-row-height: {average_height}px; --max-height: {max_height}px"
|
|
285
214
|
>
|
|
286
215
|
<thead class="thead" bind:offsetHeight={head_height}>
|
|
@@ -75,6 +75,11 @@ export function create_drag_handlers(
|
|
|
75
75
|
handle_mouse_move(event: MouseEvent): void {
|
|
76
76
|
if (!state.drag_start || !state.mouse_down_pos) return;
|
|
77
77
|
|
|
78
|
+
if (!(event.buttons & 1)) {
|
|
79
|
+
end_drag(event);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
78
83
|
const dx = Math.abs(event.clientX - state.mouse_down_pos.x);
|
|
79
84
|
const dy = Math.abs(event.clientY - state.mouse_down_pos.y);
|
|
80
85
|
|
|
@@ -37,7 +37,12 @@ export async function handle_cell_blur(
|
|
|
37
37
|
const input_el = event.target as HTMLInputElement;
|
|
38
38
|
if (!input_el || input_el.value === undefined) return;
|
|
39
39
|
|
|
40
|
-
await save_cell_value(
|
|
40
|
+
await save_cell_value(
|
|
41
|
+
input_el.type === "checkbox" ? String(input_el.checked) : input_el.value,
|
|
42
|
+
ctx,
|
|
43
|
+
coords[0],
|
|
44
|
+
coords[1]
|
|
45
|
+
);
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
function handle_header_navigation(
|