@gradio/dataframe 0.23.0 → 0.23.2
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 +26 -0
- package/Dataframe.test.ts +316 -4
- package/Index.svelte +0 -1
- package/dist/Index.svelte +0 -1
- package/dist/shared/BooleanCell.svelte +0 -1
- package/dist/shared/DataCell.svelte +88 -3
- package/dist/shared/DataCell.svelte.d.ts +1 -0
- package/dist/shared/EditableCell.svelte +52 -29
- package/dist/shared/EditableCell.svelte.d.ts +2 -0
- package/dist/shared/HeaderCell.svelte +67 -5
- package/dist/shared/HeaderCell.svelte.d.ts +4 -0
- package/dist/shared/Table.svelte +166 -50
- package/dist/shared/column_measurement.svelte.d.ts +1 -0
- package/dist/shared/column_measurement.svelte.js +86 -13
- package/package.json +7 -7
- package/shared/BooleanCell.svelte +0 -1
- package/shared/DataCell.svelte +88 -3
- package/shared/EditableCell.svelte +52 -29
- package/shared/HeaderCell.svelte +67 -5
- package/shared/Table.svelte +166 -50
- package/shared/column_measurement.svelte.ts +98 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @gradio/dataframe
|
|
2
2
|
|
|
3
|
+
## 0.23.2
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- [#13250](https://github.com/gradio-app/gradio/pull/13250) [`2e9eb51`](https://github.com/gradio-app/gradio/commit/2e9eb51d4da5e4a997ac2fc18e5814bacf6f818d) - Restore select-all checkbox in boolean column headers of `gr.Dataframe`. Thanks @pngwn!
|
|
8
|
+
- [#13259](https://github.com/gradio-app/gradio/pull/13259) [`b5e6491`](https://github.com/gradio-app/gradio/commit/b5e64918b721550bc3762169fda81c5270904eaa) - Dataframe: allow native context menu and text selection on static cells. Thanks @pngwn!
|
|
9
|
+
- [#13305](https://github.com/gradio-app/gradio/pull/13305) [`1d5994d`](https://github.com/gradio-app/gradio/commit/1d5994da102bd9bf2367f58789de508b1727b1bf) - Dataframe: fix extreme rendering slowdown with `datatype="date"` (and any future dtype with asymmetric string casts) by firing `EditableCell`'s shim-blur only on edit teardown instead of every render. Also makes the hidden sizing-row computation faster by avoiding Date rendering for every entry. Thanks @pngwn!
|
|
10
|
+
- [#13303](https://github.com/gradio-app/gradio/pull/13303) [`79c5776`](https://github.com/gradio-app/gradio/commit/79c577680978adfb5eeaa01944635e14dc67e5f1) - Rework `Dataframe` wrapping, truncation, and column width sizing. Thanks @pngwn!
|
|
11
|
+
|
|
12
|
+
### Dependency updates
|
|
13
|
+
|
|
14
|
+
- @gradio/atoms@0.24.0
|
|
15
|
+
- @gradio/statustracker@0.14.1
|
|
16
|
+
- @gradio/checkbox@0.7.0
|
|
17
|
+
- @gradio/upload@0.17.9
|
|
18
|
+
|
|
19
|
+
## 0.23.1
|
|
20
|
+
|
|
21
|
+
### Dependency updates
|
|
22
|
+
|
|
23
|
+
- @gradio/atoms@0.23.1
|
|
24
|
+
- @gradio/statustracker@0.14.0
|
|
25
|
+
- @gradio/client@2.2.0
|
|
26
|
+
- @gradio/markdown-code@0.7.0
|
|
27
|
+
- @gradio/checkbox@0.6.7
|
|
28
|
+
|
|
3
29
|
## 0.23.0
|
|
4
30
|
|
|
5
31
|
### Features
|
package/Dataframe.test.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { test, describe, afterEach, expect } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
cleanup,
|
|
4
|
+
render,
|
|
5
|
+
fireEvent,
|
|
6
|
+
waitFor,
|
|
7
|
+
within
|
|
8
|
+
} from "@self/tootils/render";
|
|
3
9
|
import { tick } from "svelte";
|
|
4
10
|
|
|
5
11
|
import Dataframe from "./Index.svelte";
|
|
@@ -284,6 +290,85 @@ describe("Cell selection", () => {
|
|
|
284
290
|
expect(get_cell(container, 1, 0)!.className).toContain("cell-selected");
|
|
285
291
|
expect(get_cell(container, 1, 1)!.className).toContain("cell-selected");
|
|
286
292
|
});
|
|
293
|
+
|
|
294
|
+
// Regression: pressing Ctrl between mousedown and mouseup must not call
|
|
295
|
+
// handle_cell_click a second time via the click event. If it did, the
|
|
296
|
+
// Ctrl+click path would toggle the already-selected cell back off,
|
|
297
|
+
// producing conflicting state updates that froze the interface.
|
|
298
|
+
test("pressing Ctrl between mousedown and mouseup keeps cell selected", async () => {
|
|
299
|
+
const { container } = await render(Dataframe, default_props);
|
|
300
|
+
await wait();
|
|
301
|
+
|
|
302
|
+
const cell = get_cell(container, 0, 0)!;
|
|
303
|
+
|
|
304
|
+
// mousedown (no ctrl) selects the cell
|
|
305
|
+
await fireEvent.mouseDown(cell);
|
|
306
|
+
await wait();
|
|
307
|
+
expect(cell.className).toContain("cell-selected");
|
|
308
|
+
|
|
309
|
+
// The browser fires a click event when the user releases the mouse button.
|
|
310
|
+
// If the user pressed Ctrl before releasing, that click carries ctrlKey:true.
|
|
311
|
+
// A second call to handle_cell_click with ctrlKey would toggle the cell
|
|
312
|
+
// back off (deselect it). Verify that the click event has no effect.
|
|
313
|
+
await fireEvent.click(cell, { ctrlKey: true });
|
|
314
|
+
await wait();
|
|
315
|
+
|
|
316
|
+
expect(cell.className).toContain("cell-selected");
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe("Header overflow", () => {
|
|
321
|
+
let wrapper: HTMLDivElement;
|
|
322
|
+
|
|
323
|
+
afterEach(async () => {
|
|
324
|
+
cleanup();
|
|
325
|
+
wrapper?.remove();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Headers must expand to fit their text (no ellipsis truncation).
|
|
329
|
+
// When headers are wider than the scroll container, later columns are
|
|
330
|
+
// hidden by the container's overflow — not by cutting off the text.
|
|
331
|
+
test("long headers are not truncated in the DOM and the 3rd header is hidden by scroll overflow", async () => {
|
|
332
|
+
// Narrow container makes overflow predictable regardless of viewport size.
|
|
333
|
+
wrapper = document.createElement("div");
|
|
334
|
+
wrapper.style.width = "300px";
|
|
335
|
+
document.body.appendChild(wrapper);
|
|
336
|
+
|
|
337
|
+
// Capital W is one of the widest glyphs — 60 of them easily exceeds 300 px.
|
|
338
|
+
const long_header = "W".repeat(60);
|
|
339
|
+
|
|
340
|
+
const { getAllByRole } = await render(
|
|
341
|
+
Dataframe,
|
|
342
|
+
{
|
|
343
|
+
...default_props,
|
|
344
|
+
value: {
|
|
345
|
+
data: [["a", "b", "c"]],
|
|
346
|
+
headers: [long_header + " A", long_header + " B", long_header + " C"],
|
|
347
|
+
metadata: null
|
|
348
|
+
},
|
|
349
|
+
col_count: [3, "fixed"] as [number, "fixed" | "dynamic"],
|
|
350
|
+
row_count: [1, "fixed"] as [number, "fixed" | "dynamic"]
|
|
351
|
+
},
|
|
352
|
+
{ container: wrapper }
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
await wait();
|
|
356
|
+
|
|
357
|
+
const headers = getAllByRole("columnheader");
|
|
358
|
+
expect(headers).toHaveLength(3);
|
|
359
|
+
|
|
360
|
+
// Full text must be present in the DOM — no in-cell truncation.
|
|
361
|
+
expect(headers[2].textContent?.trim()).toContain(long_header + " C");
|
|
362
|
+
|
|
363
|
+
// The 3rd header's left edge must be beyond the scroll container's right
|
|
364
|
+
// edge, meaning it is completely outside the visible area.
|
|
365
|
+
const viewport = wrapper.querySelector(
|
|
366
|
+
".virtual-table-viewport"
|
|
367
|
+
) as HTMLElement;
|
|
368
|
+
const viewport_rect = viewport.getBoundingClientRect();
|
|
369
|
+
const third_rect = headers[2].getBoundingClientRect();
|
|
370
|
+
expect(third_rect.left).toBeGreaterThanOrEqual(viewport_rect.right);
|
|
371
|
+
});
|
|
287
372
|
});
|
|
288
373
|
|
|
289
374
|
describe("Sorting", () => {
|
|
@@ -500,6 +585,67 @@ describe("Add/remove rows and columns", () => {
|
|
|
500
585
|
});
|
|
501
586
|
});
|
|
502
587
|
|
|
588
|
+
describe("Cell menu visibility on static cells", () => {
|
|
589
|
+
afterEach(() => cleanup());
|
|
590
|
+
|
|
591
|
+
test("context menu does not open on a static column cell", async () => {
|
|
592
|
+
const { container } = await render(Dataframe, {
|
|
593
|
+
...default_props,
|
|
594
|
+
static_columns: [0]
|
|
595
|
+
});
|
|
596
|
+
await wait();
|
|
597
|
+
|
|
598
|
+
const cell = get_cell(container, 0, 0)!;
|
|
599
|
+
await fireEvent.contextMenu(cell);
|
|
600
|
+
await wait();
|
|
601
|
+
|
|
602
|
+
expect(within(document.body).queryByRole("menu")).toBeNull();
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
test("context menu does not open when interactive is false", async () => {
|
|
606
|
+
const { container } = await render(Dataframe, {
|
|
607
|
+
...default_props,
|
|
608
|
+
interactive: false,
|
|
609
|
+
editable: false
|
|
610
|
+
});
|
|
611
|
+
await wait();
|
|
612
|
+
|
|
613
|
+
const cell = get_cell(container, 0, 0)!;
|
|
614
|
+
await fireEvent.contextMenu(cell);
|
|
615
|
+
await wait();
|
|
616
|
+
|
|
617
|
+
expect(within(document.body).queryByRole("menu")).toBeNull();
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
test("context menu does open on an editable, non-static cell", async () => {
|
|
621
|
+
const { container } = await render(Dataframe, {
|
|
622
|
+
...default_props,
|
|
623
|
+
row_count: [3, "dynamic"] as [number, "fixed" | "dynamic"]
|
|
624
|
+
});
|
|
625
|
+
await wait();
|
|
626
|
+
|
|
627
|
+
const cell = get_cell(container, 0, 0)!;
|
|
628
|
+
await fireEvent.contextMenu(cell);
|
|
629
|
+
await wait();
|
|
630
|
+
|
|
631
|
+
expect(within(document.body).queryByRole("menu")).not.toBeNull();
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
test("cell menu button does not render on a static column cell", async () => {
|
|
635
|
+
const { container } = await render(Dataframe, {
|
|
636
|
+
...default_props,
|
|
637
|
+
static_columns: [0]
|
|
638
|
+
});
|
|
639
|
+
await wait();
|
|
640
|
+
|
|
641
|
+
const cell = get_cell(container, 0, 0)!;
|
|
642
|
+
await fireEvent.mouseDown(cell);
|
|
643
|
+
await wait();
|
|
644
|
+
|
|
645
|
+
expect(cell.querySelector(".cell-menu-button")).toBeNull();
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
|
|
503
649
|
describe("Delete/clear cells", () => {
|
|
504
650
|
afterEach(() => cleanup());
|
|
505
651
|
|
|
@@ -701,9 +847,175 @@ describe("Copy", () => {
|
|
|
701
847
|
|
|
702
848
|
const table_wrap = get_table_wrap(container)!;
|
|
703
849
|
await fireEvent.keyDown(table_wrap, { key: "c", metaKey: true });
|
|
704
|
-
await wait(100);
|
|
705
850
|
|
|
706
|
-
|
|
707
|
-
|
|
851
|
+
// handle_copy() is async but called without await in the keydown handler,
|
|
852
|
+
// so the clipboard write may not be complete immediately. Poll until it settles.
|
|
853
|
+
await waitFor(async () => {
|
|
854
|
+
expect(await navigator.clipboard.readText()).toBe("Alice");
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
describe("Boolean column select-all header checkbox", () => {
|
|
860
|
+
afterEach(() => cleanup());
|
|
861
|
+
|
|
862
|
+
const bool_props = {
|
|
863
|
+
...default_props,
|
|
864
|
+
value: {
|
|
865
|
+
data: [
|
|
866
|
+
["Alice", true, false],
|
|
867
|
+
["Bob", false, false],
|
|
868
|
+
["Carol", true, false]
|
|
869
|
+
],
|
|
870
|
+
headers: ["Name", "Active", "Admin"],
|
|
871
|
+
metadata: null
|
|
872
|
+
},
|
|
873
|
+
datatype: ["str", "bool", "bool"] as any,
|
|
874
|
+
col_count: [3, "fixed"] as [number, "fixed" | "dynamic"]
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
function get_header(container: HTMLElement, col_idx: number): HTMLElement {
|
|
878
|
+
return container.querySelector<HTMLElement>(
|
|
879
|
+
`th[data-heading='${col_idx}']`
|
|
880
|
+
)!;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function get_header_checkbox(
|
|
884
|
+
container: HTMLElement,
|
|
885
|
+
col_idx: number
|
|
886
|
+
): HTMLInputElement {
|
|
887
|
+
return within(get_header(container, col_idx)).getByTestId(
|
|
888
|
+
"checkbox"
|
|
889
|
+
) as HTMLInputElement;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
test("renders checkbox in bool column header (interactive mode)", async () => {
|
|
893
|
+
const { container } = await render(Dataframe, bool_props);
|
|
894
|
+
await wait();
|
|
895
|
+
|
|
896
|
+
expect(get_header_checkbox(container, 1)).toBeInTheDocument();
|
|
897
|
+
expect(get_header_checkbox(container, 2)).toBeInTheDocument();
|
|
898
|
+
|
|
899
|
+
expect(
|
|
900
|
+
within(get_header(container, 0)).queryByTestId("checkbox")
|
|
901
|
+
).not.toBeInTheDocument();
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
test("does not render checkbox when not interactive", async () => {
|
|
905
|
+
const { container } = await render(Dataframe, {
|
|
906
|
+
...bool_props,
|
|
907
|
+
interactive: false
|
|
908
|
+
});
|
|
909
|
+
await wait();
|
|
910
|
+
|
|
911
|
+
expect(
|
|
912
|
+
within(get_header(container, 1)).queryByTestId("checkbox")
|
|
913
|
+
).not.toBeInTheDocument();
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
test("header checkbox reflects indeterminate state for mixed column", async () => {
|
|
917
|
+
const { container } = await render(Dataframe, bool_props);
|
|
918
|
+
await wait();
|
|
919
|
+
|
|
920
|
+
const active = get_header_checkbox(container, 1);
|
|
921
|
+
expect(active.indeterminate).toBe(true);
|
|
922
|
+
expect(active.checked).toBe(false);
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
test("header checkbox is checked when all column values are true", async () => {
|
|
926
|
+
const { container } = await render(Dataframe, {
|
|
927
|
+
...bool_props,
|
|
928
|
+
value: {
|
|
929
|
+
data: [
|
|
930
|
+
["Alice", true],
|
|
931
|
+
["Bob", true]
|
|
932
|
+
],
|
|
933
|
+
headers: ["Name", "Active"],
|
|
934
|
+
metadata: null
|
|
935
|
+
},
|
|
936
|
+
datatype: ["str", "bool"] as any,
|
|
937
|
+
col_count: [2, "fixed"] as [number, "fixed" | "dynamic"]
|
|
938
|
+
});
|
|
939
|
+
await wait();
|
|
940
|
+
|
|
941
|
+
const active = get_header_checkbox(container, 1);
|
|
942
|
+
expect(active.checked).toBe(true);
|
|
943
|
+
expect(active.indeterminate).toBe(false);
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
test("clicking indeterminate header checkbox sets all column cells to true", async () => {
|
|
947
|
+
const { container, listen } = await render(Dataframe, bool_props);
|
|
948
|
+
await wait();
|
|
949
|
+
|
|
950
|
+
const change = listen("change");
|
|
951
|
+
await fireEvent.click(get_header_checkbox(container, 1));
|
|
952
|
+
await wait();
|
|
953
|
+
|
|
954
|
+
const data = change.mock.calls.at(-1)?.[0].data;
|
|
955
|
+
expect(data).toEqual([
|
|
956
|
+
["Alice", true, false],
|
|
957
|
+
["Bob", true, false],
|
|
958
|
+
["Carol", true, false]
|
|
959
|
+
]);
|
|
960
|
+
|
|
961
|
+
const updated = get_header_checkbox(container, 1);
|
|
962
|
+
expect(updated.checked).toBe(true);
|
|
963
|
+
expect(updated.indeterminate).toBe(false);
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
test("clicking fully-checked header checkbox sets all column cells to false", async () => {
|
|
967
|
+
const { container, listen } = await render(Dataframe, {
|
|
968
|
+
...bool_props,
|
|
969
|
+
value: {
|
|
970
|
+
data: [
|
|
971
|
+
["Alice", true, false],
|
|
972
|
+
["Bob", true, false]
|
|
973
|
+
],
|
|
974
|
+
headers: ["Name", "Active", "Admin"],
|
|
975
|
+
metadata: null
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
await wait();
|
|
979
|
+
|
|
980
|
+
const change = listen("change");
|
|
981
|
+
await fireEvent.click(get_header_checkbox(container, 1));
|
|
982
|
+
await wait();
|
|
983
|
+
|
|
984
|
+
const data = change.mock.calls.at(-1)?.[0].data;
|
|
985
|
+
expect(data).toEqual([
|
|
986
|
+
["Alice", false, false],
|
|
987
|
+
["Bob", false, false]
|
|
988
|
+
]);
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
test("toggling one bool header does not affect cells in other bool columns", async () => {
|
|
992
|
+
const { container, listen } = await render(Dataframe, {
|
|
993
|
+
...bool_props,
|
|
994
|
+
value: {
|
|
995
|
+
data: [
|
|
996
|
+
["Alice", false, true],
|
|
997
|
+
["Bob", false, true],
|
|
998
|
+
["Carol", false, true]
|
|
999
|
+
],
|
|
1000
|
+
headers: ["Name", "Active", "Admin"],
|
|
1001
|
+
metadata: null
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
await wait();
|
|
1005
|
+
|
|
1006
|
+
const change = listen("change");
|
|
1007
|
+
await fireEvent.click(get_header_checkbox(container, 1));
|
|
1008
|
+
await wait();
|
|
1009
|
+
|
|
1010
|
+
const data = change.mock.calls.at(-1)?.[0].data;
|
|
1011
|
+
expect(data).toEqual([
|
|
1012
|
+
["Alice", true, true],
|
|
1013
|
+
["Bob", true, true],
|
|
1014
|
+
["Carol", true, true]
|
|
1015
|
+
]);
|
|
1016
|
+
|
|
1017
|
+
const admin = get_header_checkbox(container, 2);
|
|
1018
|
+
expect(admin.checked).toBe(true);
|
|
1019
|
+
expect(admin.indeterminate).toBe(false);
|
|
708
1020
|
});
|
|
709
1021
|
});
|
package/Index.svelte
CHANGED
package/dist/Index.svelte
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
show_menu_button = false,
|
|
21
21
|
show_selection_buttons = false,
|
|
22
22
|
is_first_column = false,
|
|
23
|
+
is_solo = false,
|
|
23
24
|
latex_delimiters,
|
|
24
25
|
line_breaks = true,
|
|
25
26
|
editable = true,
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
show_menu_button?: boolean;
|
|
51
52
|
show_selection_buttons?: boolean;
|
|
52
53
|
is_first_column?: boolean;
|
|
54
|
+
is_solo?: boolean;
|
|
53
55
|
latex_delimiters: { left: string; right: string; display: boolean }[];
|
|
54
56
|
line_breaks?: boolean;
|
|
55
57
|
editable?: boolean;
|
|
@@ -71,21 +73,38 @@
|
|
|
71
73
|
} = $props();
|
|
72
74
|
|
|
73
75
|
let is_selected = $derived(selection_classes !== "");
|
|
76
|
+
|
|
77
|
+
// lock the cell-wrap's height to its pre-selection value so the
|
|
78
|
+
// row doesn't shift when the span goes position: absolute.
|
|
79
|
+
let wrap_el: HTMLDivElement | undefined = $state();
|
|
80
|
+
let locked_height = $state("");
|
|
81
|
+
|
|
82
|
+
// pre is weird but runs _before_ DOM updates
|
|
83
|
+
$effect.pre(() => {
|
|
84
|
+
if (is_solo && wrap_el) {
|
|
85
|
+
locked_height = `height: ${wrap_el.offsetHeight}px;`;
|
|
86
|
+
} else {
|
|
87
|
+
locked_height = "";
|
|
88
|
+
}
|
|
89
|
+
});
|
|
74
90
|
</script>
|
|
75
91
|
|
|
76
92
|
<div
|
|
77
93
|
class="body-cell {selection_classes}"
|
|
78
94
|
class:flash={is_flash}
|
|
79
95
|
class:first-column={is_first_column}
|
|
96
|
+
class:static={is_static}
|
|
97
|
+
class:cell-solo={is_solo}
|
|
80
98
|
data-row={row_idx}
|
|
81
99
|
data-col={col_idx}
|
|
82
100
|
data-testid={`cell-${row_idx}-${col_idx}`}
|
|
101
|
+
tabindex={is_static ? -1 : undefined}
|
|
83
102
|
{onmousedown}
|
|
84
103
|
{ondblclick}
|
|
85
104
|
{oncontextmenu}
|
|
86
105
|
style="{col_style} {cell_style}"
|
|
87
106
|
>
|
|
88
|
-
<div class="cell-wrap">
|
|
107
|
+
<div class="cell-wrap" bind:this={wrap_el} style={locked_height}>
|
|
89
108
|
<EditableCell
|
|
90
109
|
{value}
|
|
91
110
|
{display_value}
|
|
@@ -105,6 +124,7 @@
|
|
|
105
124
|
{on_select_row}
|
|
106
125
|
{is_dragging}
|
|
107
126
|
{wrap_text}
|
|
127
|
+
expanded={is_solo}
|
|
108
128
|
/>
|
|
109
129
|
{#if show_menu_button}
|
|
110
130
|
<CellMenuButton on_click={on_menu_click} />
|
|
@@ -120,8 +140,14 @@
|
|
|
120
140
|
inset 1px 0 0 var(--border-color-primary),
|
|
121
141
|
inset 0 0 0 1px var(--ring-color);
|
|
122
142
|
padding: 0;
|
|
123
|
-
overflow:
|
|
143
|
+
overflow: hidden;
|
|
124
144
|
box-sizing: border-box;
|
|
145
|
+
user-select: none;
|
|
146
|
+
min-width: 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.body-cell.static {
|
|
150
|
+
user-select: text;
|
|
125
151
|
}
|
|
126
152
|
|
|
127
153
|
.body-cell.first-column {
|
|
@@ -145,6 +171,65 @@
|
|
|
145
171
|
position: relative;
|
|
146
172
|
}
|
|
147
173
|
|
|
174
|
+
.body-cell.cell-solo {
|
|
175
|
+
z-index: 10;
|
|
176
|
+
overflow: visible;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.body-cell.cell-solo .cell-wrap {
|
|
180
|
+
overflow: visible;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.body-cell.cell-solo :global(.cell-wrap > span) {
|
|
184
|
+
position: absolute;
|
|
185
|
+
top: 0;
|
|
186
|
+
left: 0;
|
|
187
|
+
width: 100%;
|
|
188
|
+
min-height: 100%;
|
|
189
|
+
padding: var(--size-2);
|
|
190
|
+
box-sizing: border-box;
|
|
191
|
+
white-space: normal;
|
|
192
|
+
overflow: visible;
|
|
193
|
+
text-overflow: clip;
|
|
194
|
+
overflow-wrap: break-word;
|
|
195
|
+
word-break: break-word;
|
|
196
|
+
background: var(--background-fill-primary);
|
|
197
|
+
box-shadow:
|
|
198
|
+
inset 0 2px 0 0 var(--color-accent),
|
|
199
|
+
inset 0 -2px 0 0 var(--color-accent),
|
|
200
|
+
inset 2px 0 0 0 var(--color-accent),
|
|
201
|
+
inset -2px 0 0 0 var(--color-accent),
|
|
202
|
+
0 6px 12px -4px rgba(0, 0, 0, 0.22);
|
|
203
|
+
z-index: 1;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.body-cell.cell-solo :global(.cell-wrap > textarea) {
|
|
207
|
+
position: absolute;
|
|
208
|
+
top: 0;
|
|
209
|
+
left: 0;
|
|
210
|
+
width: 100%;
|
|
211
|
+
min-height: 100%;
|
|
212
|
+
padding: var(--size-2);
|
|
213
|
+
padding-left: var(--size-2);
|
|
214
|
+
margin: 0;
|
|
215
|
+
box-sizing: border-box;
|
|
216
|
+
transform: none;
|
|
217
|
+
background: var(--background-fill-primary);
|
|
218
|
+
box-shadow:
|
|
219
|
+
inset 0 2px 0 0 var(--color-accent),
|
|
220
|
+
inset 0 -2px 0 0 var(--color-accent),
|
|
221
|
+
inset 2px 0 0 0 var(--color-accent),
|
|
222
|
+
inset -2px 0 0 0 var(--color-accent),
|
|
223
|
+
0 6px 12px -4px rgba(0, 0, 0, 0.22);
|
|
224
|
+
z-index: 2;
|
|
225
|
+
field-sizing: content;
|
|
226
|
+
height: auto;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.body-cell.cell-solo :global(.selection-button) {
|
|
230
|
+
z-index: 3;
|
|
231
|
+
}
|
|
232
|
+
|
|
148
233
|
.body-cell.cell-selected.no-top {
|
|
149
234
|
--sel-top: inset 0 0 0 0 transparent;
|
|
150
235
|
}
|
|
@@ -182,7 +267,7 @@
|
|
|
182
267
|
padding: var(--size-2);
|
|
183
268
|
box-sizing: border-box;
|
|
184
269
|
gap: var(--size-1);
|
|
185
|
-
overflow:
|
|
270
|
+
overflow: hidden;
|
|
186
271
|
min-width: 0;
|
|
187
272
|
height: 100%;
|
|
188
273
|
}
|