@marimo-team/islands 0.23.9-dev4 → 0.23.9-dev41
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/dist/{ConnectedDataExplorerComponent-OzrfMM5L.js → ConnectedDataExplorerComponent-CyV83R2m.js} +4 -4
- package/dist/assets/__vite-browser-external-Ci2ZQfXU.js +1 -0
- package/dist/assets/{worker-CpBbwbQo.js → worker-ip3AI_sN.js} +2 -2
- package/dist/{chat-ui-BDI3FMI8.js → chat-ui-ChD4VvCo.js} +3060 -3033
- package/dist/{code-visibility-VZebNmSs.js → code-visibility-s3OWNKLN.js} +1634 -1314
- package/dist/{formats-DQ5qjo_Q.js → formats-DHxc-FdY.js} +1 -1
- package/dist/{glide-data-editor-DqRY9naW.js → glide-data-editor-BOmK9ETQ.js} +2 -2
- package/dist/{html-to-image-CiSinpSR.js → html-to-image-BHv7CEU_.js} +2145 -2153
- package/dist/{input-CZD2z6X2.js → input-_2sjvfne.js} +1 -1
- package/dist/main.js +1522 -1556
- package/dist/{mermaid-IU93XzmY.js → mermaid-lXOw5Py9.js} +2 -2
- package/dist/{process-output-5qJjMRKh.js → process-output-BvySRgli.js} +33 -25
- package/dist/{reveal-component-DZtPMEoM.js → reveal-component-DCO6zm_5.js} +17 -17
- package/dist/{spec-a6DaqW__.js → spec-B96zNUEA.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/{toDate-ZVVIBmdk.js → toDate-x-WRDCH7.js} +1 -1
- package/dist/{useAsyncData-C008zUPi.js → useAsyncData-iRgKDT5s.js} +1 -1
- package/dist/{useDeepCompareMemoize-BrA3_n61.js → useDeepCompareMemoize-CkQ57VS2.js} +1 -1
- package/dist/{useLifecycle-BNaoJ5a4.js → useLifecycle-BBO9PIph.js} +1 -1
- package/dist/{useTheme-7O0YWlE5.js → useTheme-DHIrRQOe.js} +34 -21
- package/dist/{vega-component-DJNmOdUj.js → vega-component-Dq-SH463.js} +5 -5
- package/package.json +1 -1
- package/src/components/ai/__tests__/ai-utils.test.ts +43 -38
- package/src/components/ai/ai-model-dropdown.tsx +2 -2
- package/src/components/app-config/ai-config.tsx +147 -16
- package/src/components/app-config/user-config-form.tsx +37 -1
- package/src/components/chat/__tests__/chat-utils.test.ts +269 -0
- package/src/components/chat/chat-panel.tsx +38 -5
- package/src/components/chat/chat-utils.ts +14 -58
- package/src/components/data-table/TableBottomBar.tsx +27 -6
- package/src/components/data-table/TableTopBar.tsx +7 -1
- package/src/components/data-table/__tests__/TableBottomBar.test.tsx +73 -0
- package/src/components/data-table/__tests__/column-explorer.test.tsx +128 -0
- package/src/components/data-table/__tests__/data-table.test.tsx +52 -1
- package/src/components/data-table/__tests__/header-items.test.tsx +257 -1
- package/src/components/data-table/__tests__/useColumnVisibility.test.ts +42 -0
- package/src/components/data-table/column-explorer-panel/column-explorer.tsx +98 -26
- package/src/components/data-table/column-header.tsx +19 -12
- package/src/components/data-table/columns.tsx +3 -4
- package/src/components/data-table/data-table.tsx +37 -0
- package/src/components/data-table/export-actions.tsx +36 -18
- package/src/components/data-table/header-items.tsx +58 -17
- package/src/components/data-table/hooks/use-column-visibility.ts +56 -0
- package/src/components/data-table/pagination.tsx +16 -3
- package/src/components/data-table/schemas.ts +2 -2
- package/src/components/data-table/table-explorer-panel/table-explorer-panel.tsx +16 -6
- package/src/components/databases/display.tsx +2 -0
- package/src/components/datasources/__tests__/utils.test.ts +82 -0
- package/src/components/datasources/utils.ts +16 -15
- package/src/components/editor/actions/pair-with-agent-modal.tsx +1 -0
- package/src/components/editor/actions/useCellActionButton.tsx +3 -3
- package/src/components/editor/actions/useNotebookActions.tsx +5 -2
- package/src/components/editor/cell/code/cell-editor.tsx +7 -4
- package/src/components/editor/chrome/types.ts +13 -6
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +6 -4
- package/src/components/editor/chrome/wrapper/footer-items/ai-status.tsx +10 -1
- package/src/components/editor/chrome/wrapper/sidebar.tsx +7 -5
- package/src/components/editor/errors/auto-fix.tsx +3 -3
- package/src/components/editor/errors/mangled-local-chip.tsx +50 -0
- package/src/components/editor/navigation/__tests__/navigation.test.ts +15 -0
- package/src/components/editor/navigation/navigation.ts +5 -0
- package/src/components/editor/output/MarimoErrorOutput.tsx +110 -27
- package/src/components/editor/output/MarimoTracebackOutput.tsx +55 -37
- package/src/components/editor/renderers/cell-array.tsx +27 -24
- package/src/components/editor/renderers/slides-layout/slides-layout.tsx +1 -1
- package/src/components/slides/reveal-component.tsx +3 -3
- package/src/components/slides/slide-form.tsx +11 -3
- package/src/components/static-html/static-banner.tsx +28 -22
- package/src/core/ai/__tests__/model-registry.test.ts +72 -60
- package/src/core/ai/model-registry.ts +33 -28
- package/src/core/cells/__tests__/actions.test.ts +48 -0
- package/src/core/cells/actions.ts +5 -6
- package/src/core/codemirror/__tests__/setup.test.ts +29 -0
- package/src/core/codemirror/cells/traceback-decorations.ts +1 -1
- package/src/core/codemirror/cm.ts +3 -2
- package/src/core/codemirror/completion/hints.ts +4 -1
- package/src/core/codemirror/format.ts +1 -0
- package/src/core/codemirror/keymaps/vim.ts +63 -0
- package/src/core/codemirror/language/languages/sql/sql.ts +1 -0
- package/src/core/codemirror/language/languages/sql/utils.ts +2 -0
- package/src/core/config/__tests__/config-schema.test.ts +4 -0
- package/src/core/config/config-schema.ts +4 -0
- package/src/core/config/config.ts +16 -0
- package/src/css/app/Cell.css +0 -1
- package/src/plugins/impl/DataTablePlugin.tsx +94 -33
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +1 -0
- package/src/plugins/impl/chat/ChatPlugin.tsx +7 -1
- package/src/plugins/impl/chat/__tests__/chat-ui.test.ts +278 -0
- package/src/plugins/impl/chat/chat-ui.tsx +106 -59
- package/src/plugins/impl/chat/types.ts +5 -0
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +8 -6
- package/src/stories/dataframe.stories.tsx +1 -0
- package/src/utils/__tests__/json-parser.test.ts +1 -69
- package/src/utils/__tests__/local-variables.test.ts +132 -0
- package/src/utils/json/json-parser.ts +0 -30
- package/src/utils/local-variables.ts +67 -0
- package/dist/assets/__vite-browser-external-CAdMKBac.js +0 -1
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
"use no memo";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
// tanstack/table is not compatible with React compiler
|
|
5
|
+
// https://github.com/TanStack/table/issues/5567
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
ChevronDownIcon,
|
|
9
|
+
ChevronRightIcon,
|
|
10
|
+
EyeIcon,
|
|
11
|
+
EyeOffIcon,
|
|
12
|
+
} from "lucide-react";
|
|
4
13
|
import { useState } from "react";
|
|
5
14
|
import { useLocale } from "react-aria";
|
|
6
15
|
import {
|
|
@@ -38,22 +47,28 @@ import {
|
|
|
38
47
|
INDEX_COLUMN_NAME,
|
|
39
48
|
SELECT_COLUMN_ID,
|
|
40
49
|
} from "../types";
|
|
50
|
+
import { smartMatch } from "@/utils/smartMatch";
|
|
51
|
+
import type { Column, Table } from "@tanstack/react-table";
|
|
52
|
+
import { cn } from "@/utils/cn";
|
|
53
|
+
import { getColumnCountForDisplay } from "../hooks/use-column-visibility";
|
|
41
54
|
|
|
42
|
-
interface ColumnExplorerPanelProps {
|
|
55
|
+
interface ColumnExplorerPanelProps<TData> {
|
|
43
56
|
previewColumn: PreviewColumn;
|
|
44
57
|
fieldTypes: FieldTypesWithExternalType | undefined | null;
|
|
45
58
|
totalRows: number | "too_many";
|
|
46
59
|
totalColumns: number;
|
|
47
60
|
tableId: string;
|
|
61
|
+
table: Table<TData>;
|
|
48
62
|
}
|
|
49
63
|
|
|
50
|
-
export
|
|
64
|
+
export function ColumnExplorerPanel<TData>({
|
|
51
65
|
previewColumn,
|
|
52
66
|
fieldTypes,
|
|
53
67
|
totalRows,
|
|
54
68
|
totalColumns,
|
|
55
69
|
tableId,
|
|
56
|
-
|
|
70
|
+
table,
|
|
71
|
+
}: ColumnExplorerPanelProps<TData>) {
|
|
57
72
|
const [searchValue, setSearchValue] = useState("");
|
|
58
73
|
const { locale } = useLocale();
|
|
59
74
|
const columns = fieldTypes?.filter(([columnName]) => {
|
|
@@ -68,19 +83,43 @@ export const ColumnExplorerPanel = ({
|
|
|
68
83
|
});
|
|
69
84
|
|
|
70
85
|
const filteredColumns = columns?.filter(([columnName]) => {
|
|
71
|
-
return
|
|
86
|
+
return smartMatch(searchValue, columnName);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const {
|
|
90
|
+
totalColumns: effectiveTotalColumns,
|
|
91
|
+
hiddenColumns: hiddenColumnCount,
|
|
92
|
+
} = getColumnCountForDisplay(table, totalColumns);
|
|
93
|
+
|
|
94
|
+
const { rowsAndColumns, hiddenSuffix } = prettifyRowColumnCount({
|
|
95
|
+
numRows: totalRows,
|
|
96
|
+
totalColumns: effectiveTotalColumns,
|
|
97
|
+
locale,
|
|
98
|
+
hiddenColumns: hiddenColumnCount,
|
|
72
99
|
});
|
|
73
100
|
|
|
74
101
|
return (
|
|
75
102
|
<div className="mb-3">
|
|
76
|
-
<
|
|
77
|
-
{
|
|
103
|
+
<div className="text-xs font-semibold ml-2 flex items-center gap-1">
|
|
104
|
+
{rowsAndColumns}
|
|
105
|
+
{hiddenColumnCount > 0 && <span>{hiddenSuffix}</span>}
|
|
78
106
|
<CopyClipboardIcon
|
|
79
107
|
tooltip="Copy column names"
|
|
80
108
|
value={columns?.map(([columnName]) => columnName).join(",\n") || ""}
|
|
81
|
-
className="h-3 w-3
|
|
109
|
+
className="h-3 w-3"
|
|
82
110
|
/>
|
|
83
|
-
|
|
111
|
+
{hiddenColumnCount > 0 && (
|
|
112
|
+
<Button
|
|
113
|
+
type="button"
|
|
114
|
+
variant="link"
|
|
115
|
+
size="xs"
|
|
116
|
+
className="h-auto p-0"
|
|
117
|
+
onClick={() => table.resetColumnVisibility(true)}
|
|
118
|
+
>
|
|
119
|
+
Unhide all
|
|
120
|
+
</Button>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
84
123
|
<Command className="h-5/6 bg-background" shouldFilter={false}>
|
|
85
124
|
<CommandInput
|
|
86
125
|
placeholder="Search columns..."
|
|
@@ -91,11 +130,14 @@ export const ColumnExplorerPanel = ({
|
|
|
91
130
|
<CommandEmpty>No results.</CommandEmpty>
|
|
92
131
|
{filteredColumns?.map(
|
|
93
132
|
([columnName, [dataType, externalType]], index) => {
|
|
133
|
+
const column = table.getColumn(columnName);
|
|
134
|
+
|
|
94
135
|
return (
|
|
95
136
|
<ColumnItem
|
|
96
137
|
// Tables may have the same column names, hence we use tableId to make it unique
|
|
97
138
|
key={`${tableId}-${columnName}`}
|
|
98
139
|
columnName={columnName}
|
|
140
|
+
column={column}
|
|
99
141
|
dataType={dataType}
|
|
100
142
|
externalType={externalType}
|
|
101
143
|
previewColumn={previewColumn}
|
|
@@ -108,21 +150,23 @@ export const ColumnExplorerPanel = ({
|
|
|
108
150
|
</Command>
|
|
109
151
|
</div>
|
|
110
152
|
);
|
|
111
|
-
}
|
|
153
|
+
}
|
|
112
154
|
|
|
113
|
-
|
|
155
|
+
function ColumnItem<TData>({
|
|
114
156
|
columnName,
|
|
157
|
+
column,
|
|
115
158
|
dataType,
|
|
116
159
|
externalType,
|
|
117
160
|
previewColumn,
|
|
118
161
|
defaultExpanded = false,
|
|
119
162
|
}: {
|
|
120
163
|
columnName: string;
|
|
164
|
+
column?: Column<TData, unknown>;
|
|
121
165
|
dataType: DataType;
|
|
122
166
|
externalType: string;
|
|
123
167
|
previewColumn: PreviewColumn;
|
|
124
168
|
defaultExpanded?: boolean;
|
|
125
|
-
})
|
|
169
|
+
}) {
|
|
126
170
|
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
|
127
171
|
|
|
128
172
|
const columnText = (
|
|
@@ -142,20 +186,48 @@ const ColumnItem = ({
|
|
|
142
186
|
<ChevronRightIcon className="w-3 h-3 shrink-0 text-muted-foreground" />
|
|
143
187
|
)}
|
|
144
188
|
<ColumnName columnName={columnText} dataType={dataType} />
|
|
145
|
-
<div className="ml-auto">
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
189
|
+
<div className="ml-auto flex items-center gap-0.5">
|
|
190
|
+
<CopyClipboardIcon
|
|
191
|
+
tooltip="Copy column name"
|
|
192
|
+
value={columnName}
|
|
193
|
+
className="h-3 w-3"
|
|
194
|
+
buttonClassName={cn(
|
|
195
|
+
"inline-flex items-center justify-center rounded-md h-6 w-6",
|
|
196
|
+
"group-hover:opacity-100 opacity-0 hover:bg-muted text-muted-foreground hover:text-primary",
|
|
197
|
+
)}
|
|
198
|
+
/>
|
|
199
|
+
{column?.getCanHide() && (
|
|
200
|
+
<Tooltip
|
|
201
|
+
content={column.getIsVisible() ? "Hide column" : "Show column"}
|
|
202
|
+
delayDuration={400}
|
|
151
203
|
>
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
204
|
+
<Button
|
|
205
|
+
type="button"
|
|
206
|
+
variant="text"
|
|
207
|
+
size="icon"
|
|
208
|
+
aria-label={
|
|
209
|
+
column.getIsVisible() ? "Hide column" : "Show column"
|
|
210
|
+
}
|
|
211
|
+
className={cn(
|
|
212
|
+
"hover:bg-muted text-muted-foreground hover:text-primary",
|
|
213
|
+
column.getIsVisible()
|
|
214
|
+
? "group-hover:opacity-100 opacity-0"
|
|
215
|
+
: "opacity-100",
|
|
216
|
+
)}
|
|
217
|
+
onClick={(e) => {
|
|
218
|
+
e.preventDefault();
|
|
219
|
+
e.stopPropagation();
|
|
220
|
+
column.toggleVisibility(!column.getIsVisible());
|
|
221
|
+
}}
|
|
222
|
+
>
|
|
223
|
+
{column.getIsVisible() ? (
|
|
224
|
+
<EyeIcon className="h-3 w-3" strokeWidth={2.5} />
|
|
225
|
+
) : (
|
|
226
|
+
<EyeOffIcon className="h-3 w-3" strokeWidth={2.5} />
|
|
227
|
+
)}
|
|
228
|
+
</Button>
|
|
229
|
+
</Tooltip>
|
|
230
|
+
)}
|
|
159
231
|
<span className="text-xs text-muted-foreground">{externalType}</span>
|
|
160
232
|
</div>
|
|
161
233
|
</CommandItem>
|
|
@@ -170,7 +242,7 @@ const ColumnItem = ({
|
|
|
170
242
|
)}
|
|
171
243
|
</>
|
|
172
244
|
);
|
|
173
|
-
}
|
|
245
|
+
}
|
|
174
246
|
|
|
175
247
|
const ColumnPreview = ({
|
|
176
248
|
previewColumn,
|
|
@@ -17,13 +17,14 @@ import { useFilterEditor } from "./filter-editor-context";
|
|
|
17
17
|
import { EDITABLE_FILTER_TYPES, isMembershipFilterType } from "./filters";
|
|
18
18
|
import {
|
|
19
19
|
ClearFilterMenuItem,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
ColumnPinning,
|
|
21
|
+
ColumnWrapping,
|
|
22
|
+
CopyColumn,
|
|
23
|
+
DataType,
|
|
24
|
+
FormatOptions,
|
|
25
|
+
HideColumn,
|
|
25
26
|
renderSortIcon,
|
|
26
|
-
|
|
27
|
+
Sorts,
|
|
27
28
|
} from "./header-items";
|
|
28
29
|
|
|
29
30
|
interface DataTableColumnHeaderProps<
|
|
@@ -35,6 +36,11 @@ interface DataTableColumnHeaderProps<
|
|
|
35
36
|
subheader?: React.ReactNode;
|
|
36
37
|
justify?: "left" | "center" | "right";
|
|
37
38
|
calculateTopKRows?: CalculateTopKRows;
|
|
39
|
+
/**
|
|
40
|
+
* Optional: only used to surface multi-column sort actions ("Clear all
|
|
41
|
+
* sorts"). Omitted by call sites that define their header inside column
|
|
42
|
+
* definitions, where the table instance isn't yet available.
|
|
43
|
+
*/
|
|
38
44
|
table?: Table<TData>;
|
|
39
45
|
}
|
|
40
46
|
|
|
@@ -118,12 +124,13 @@ export const DataTableColumnHeader = <TData, TValue>({
|
|
|
118
124
|
</button>
|
|
119
125
|
</DropdownMenuTrigger>
|
|
120
126
|
<DropdownMenuContent align="start">
|
|
121
|
-
{
|
|
122
|
-
{
|
|
123
|
-
{
|
|
124
|
-
{
|
|
125
|
-
{
|
|
126
|
-
{
|
|
127
|
+
<DataType column={column} />
|
|
128
|
+
<Sorts column={column} table={table} />
|
|
129
|
+
<CopyColumn column={column} />
|
|
130
|
+
<ColumnPinning column={column} />
|
|
131
|
+
<ColumnWrapping column={column} />
|
|
132
|
+
<FormatOptions column={column} locale={locale} />
|
|
133
|
+
<HideColumn column={column} />
|
|
127
134
|
{canEditFilter && <DropdownMenuSeparator />}
|
|
128
135
|
{canEditFilter && (
|
|
129
136
|
<DropdownMenuItem
|
|
@@ -44,9 +44,10 @@ import { detectSentinel, splitLeadingTrailingWhitespace } from "./utils";
|
|
|
44
44
|
import { uniformSample } from "./uniformSample";
|
|
45
45
|
import { MarkdownUrlDetector, UrlDetector } from "./url-detector";
|
|
46
46
|
|
|
47
|
+
export const NAMELESS_COLUMN_PREFIX = "__m_column__";
|
|
47
48
|
// Artificial limit to display long strings
|
|
49
|
+
export const SELECT_ID = "__select__";
|
|
48
50
|
const MAX_STRING_LENGTH = 50;
|
|
49
|
-
const SELECT_ID = "__select__";
|
|
50
51
|
|
|
51
52
|
function inferDataType(value: unknown): [type: DataType, displayType: string] {
|
|
52
53
|
if (typeof value === "string") {
|
|
@@ -106,8 +107,6 @@ export function inferFieldTypes<T>(items: T[]): FieldTypesWithExternalType {
|
|
|
106
107
|
return Objects.entries(fieldTypes);
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
export const NAMELESS_COLUMN_PREFIX = "__m_column__";
|
|
110
|
-
|
|
111
110
|
export function generateColumns<T>({
|
|
112
111
|
rowHeaders,
|
|
113
112
|
selection,
|
|
@@ -192,7 +191,7 @@ export function generateColumns<T>({
|
|
|
192
191
|
accessorFn: (row) => {
|
|
193
192
|
return row[key as keyof T];
|
|
194
193
|
},
|
|
195
|
-
|
|
194
|
+
enableHiding: !rowHeadersSet.has(key) && key !== "",
|
|
196
195
|
header: ({ column, table }) => {
|
|
197
196
|
const stats = chartSpecModel?.getColumnStats(key);
|
|
198
197
|
const dtype = column.columnDef.meta?.dtype;
|
|
@@ -17,12 +17,15 @@ import {
|
|
|
17
17
|
type PaginationState,
|
|
18
18
|
type RowSelectionState,
|
|
19
19
|
type SortingState,
|
|
20
|
+
type Table as TanstackTable,
|
|
20
21
|
useReactTable,
|
|
21
22
|
} from "@tanstack/react-table";
|
|
22
23
|
import React, { memo } from "react";
|
|
23
24
|
import { useLocale } from "react-aria";
|
|
24
25
|
|
|
26
|
+
import { Button } from "@/components/ui/button";
|
|
25
27
|
import { Table } from "@/components/ui/table";
|
|
28
|
+
import { Banner } from "@/plugins/impl/common/error-banner";
|
|
26
29
|
import type {
|
|
27
30
|
CalculateTopKRows,
|
|
28
31
|
GetRowIds,
|
|
@@ -63,6 +66,10 @@ import {
|
|
|
63
66
|
type TooManyRows,
|
|
64
67
|
} from "./types";
|
|
65
68
|
import { getStableRowId } from "./utils";
|
|
69
|
+
import {
|
|
70
|
+
getUserColumnVisibilityCounts,
|
|
71
|
+
useColumnVisibility,
|
|
72
|
+
} from "./hooks/use-column-visibility";
|
|
66
73
|
|
|
67
74
|
interface DataTableProps<TData> extends Partial<ExportActionProps> {
|
|
68
75
|
wrapperClassName?: string;
|
|
@@ -80,6 +87,7 @@ interface DataTableProps<TData> extends Partial<ExportActionProps> {
|
|
|
80
87
|
// JSON-serialized size of the currently-rendered data. Forwarded to
|
|
81
88
|
// ExportMenu so hosts can size-gate the Export button via downloadSizeLimitAtom.
|
|
82
89
|
sizeBytes?: number | null;
|
|
90
|
+
sizeBytesIsLoading?: boolean;
|
|
83
91
|
totalColumns: number;
|
|
84
92
|
pagination?: boolean;
|
|
85
93
|
manualPagination?: boolean; // server-side pagination
|
|
@@ -107,6 +115,7 @@ interface DataTableProps<TData> extends Partial<ExportActionProps> {
|
|
|
107
115
|
// Columns
|
|
108
116
|
freezeColumnsLeft?: string[];
|
|
109
117
|
freezeColumnsRight?: string[];
|
|
118
|
+
hiddenColumns?: string[];
|
|
110
119
|
toggleDisplayHeader?: () => void;
|
|
111
120
|
// Row viewer panel
|
|
112
121
|
viewedRowIdx?: number;
|
|
@@ -119,6 +128,7 @@ interface DataTableProps<TData> extends Partial<ExportActionProps> {
|
|
|
119
128
|
togglePanel?: (panelType: PanelType) => void;
|
|
120
129
|
isPanelOpen?: (panelType: PanelType) => boolean;
|
|
121
130
|
isAnyPanelOpen?: boolean;
|
|
131
|
+
renderTableExplorerPanel?: (table: TanstackTable<TData>) => React.ReactNode;
|
|
122
132
|
}
|
|
123
133
|
|
|
124
134
|
const DataTableInternal = <TData,>({
|
|
@@ -132,6 +142,7 @@ const DataTableInternal = <TData,>({
|
|
|
132
142
|
totalColumns,
|
|
133
143
|
totalRows,
|
|
134
144
|
sizeBytes,
|
|
145
|
+
sizeBytesIsLoading,
|
|
135
146
|
manualSorting = false,
|
|
136
147
|
sorting,
|
|
137
148
|
setSorting,
|
|
@@ -158,6 +169,7 @@ const DataTableInternal = <TData,>({
|
|
|
158
169
|
reloading,
|
|
159
170
|
freezeColumnsLeft,
|
|
160
171
|
freezeColumnsRight,
|
|
172
|
+
hiddenColumns,
|
|
161
173
|
toggleDisplayHeader,
|
|
162
174
|
showChartBuilder,
|
|
163
175
|
isChartBuilderOpen,
|
|
@@ -168,6 +180,7 @@ const DataTableInternal = <TData,>({
|
|
|
168
180
|
isAnyPanelOpen,
|
|
169
181
|
viewedRowIdx,
|
|
170
182
|
onViewedRowChange,
|
|
183
|
+
renderTableExplorerPanel,
|
|
171
184
|
}: DataTableProps<TData>) => {
|
|
172
185
|
const [showLoadingBar, setShowLoadingBar] = React.useState<boolean>(false);
|
|
173
186
|
const { locale } = useLocale();
|
|
@@ -176,6 +189,8 @@ const DataTableInternal = <TData,>({
|
|
|
176
189
|
freezeColumnsLeft,
|
|
177
190
|
freezeColumnsRight,
|
|
178
191
|
);
|
|
192
|
+
const { columnVisibility, setColumnVisibility } =
|
|
193
|
+
useColumnVisibility(hiddenColumns);
|
|
179
194
|
|
|
180
195
|
// Show loading bar only after a short delay to prevent flickering
|
|
181
196
|
React.useEffect(() => {
|
|
@@ -267,6 +282,8 @@ const DataTableInternal = <TData,>({
|
|
|
267
282
|
enableMultiCellSelection: selection === "multi-cell",
|
|
268
283
|
// pinning
|
|
269
284
|
onColumnPinningChange: setColumnPinning,
|
|
285
|
+
// col visibility
|
|
286
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
270
287
|
// focus row
|
|
271
288
|
enableFocusRow: true,
|
|
272
289
|
onFocusRowChange: onViewedRowChange,
|
|
@@ -284,6 +301,7 @@ const DataTableInternal = <TData,>({
|
|
|
284
301
|
{ pagination: { pageIndex: 0, pageSize: data.length } }),
|
|
285
302
|
rowSelection: rowSelection ?? {},
|
|
286
303
|
cellSelection: cellSelection ?? [],
|
|
304
|
+
columnVisibility,
|
|
287
305
|
cellStyling,
|
|
288
306
|
columnPinning: columnPinning,
|
|
289
307
|
cellHoverTemplate: hoverTemplate,
|
|
@@ -317,6 +335,10 @@ const DataTableInternal = <TData,>({
|
|
|
317
335
|
[table],
|
|
318
336
|
);
|
|
319
337
|
|
|
338
|
+
const visibilityCounts = getUserColumnVisibilityCounts(table);
|
|
339
|
+
const allUserColumnsHidden =
|
|
340
|
+
visibilityCounts.total > 0 && visibilityCounts.visible === 0;
|
|
341
|
+
|
|
320
342
|
return (
|
|
321
343
|
<FilterEditorProvider value={filterEditor}>
|
|
322
344
|
<div className={cn(wrapperClassName, "flex flex-col space-y-1")}>
|
|
@@ -327,6 +349,7 @@ const DataTableInternal = <TData,>({
|
|
|
327
349
|
addFilterSnapshot={addFilterSnapshot}
|
|
328
350
|
onAddFilterSnapshotChange={setAddFilterSnapshot}
|
|
329
351
|
/>
|
|
352
|
+
{renderTableExplorerPanel?.(table)}
|
|
330
353
|
<CellSelectionProvider>
|
|
331
354
|
<div
|
|
332
355
|
part="table-wrapper"
|
|
@@ -345,7 +368,20 @@ const DataTableInternal = <TData,>({
|
|
|
345
368
|
isAnyPanelOpen={isAnyPanelOpen}
|
|
346
369
|
downloadAs={downloadAs}
|
|
347
370
|
sizeBytes={sizeBytes}
|
|
371
|
+
sizeBytesIsLoading={sizeBytesIsLoading}
|
|
348
372
|
/>
|
|
373
|
+
{allUserColumnsHidden && (
|
|
374
|
+
<Banner className="mb-1 mx-2 rounded flex items-center justify-between">
|
|
375
|
+
<span>All columns are hidden.</span>
|
|
376
|
+
<Button
|
|
377
|
+
variant="link"
|
|
378
|
+
size="xs"
|
|
379
|
+
onClick={() => table.resetColumnVisibility(true)}
|
|
380
|
+
>
|
|
381
|
+
Unhide all
|
|
382
|
+
</Button>
|
|
383
|
+
</Banner>
|
|
384
|
+
)}
|
|
349
385
|
<Table
|
|
350
386
|
className={cn(
|
|
351
387
|
"relative",
|
|
@@ -377,6 +413,7 @@ const DataTableInternal = <TData,>({
|
|
|
377
413
|
getRowIds={getRowIds}
|
|
378
414
|
showPageSizeSelector={showPageSizeSelector}
|
|
379
415
|
tableLoading={reloading}
|
|
416
|
+
togglePanel={togglePanel}
|
|
380
417
|
/>
|
|
381
418
|
</div>
|
|
382
419
|
</CellSelectionProvider>
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
TableIcon,
|
|
10
10
|
} from "lucide-react";
|
|
11
11
|
import React from "react";
|
|
12
|
-
import { useLocale } from "react-aria";
|
|
13
12
|
import { downloadSizeLimitAtom } from "./download-policy/atoms";
|
|
14
13
|
import { logNever } from "@/utils/assertNever";
|
|
15
14
|
import { cn } from "@/utils/cn";
|
|
@@ -20,7 +19,6 @@ import { Filenames } from "@/utils/filenames";
|
|
|
20
19
|
import {
|
|
21
20
|
jsonParseWithSpecialChar,
|
|
22
21
|
jsonToMarkdown,
|
|
23
|
-
jsonToTSV,
|
|
24
22
|
} from "@/utils/json/json-parser";
|
|
25
23
|
import { MissingPackagePrompt } from "../datasources/missing-package-prompt";
|
|
26
24
|
import { Button } from "../ui/button";
|
|
@@ -68,7 +66,12 @@ const FILE_TYPES = {
|
|
|
68
66
|
},
|
|
69
67
|
} as const;
|
|
70
68
|
|
|
71
|
-
const downloadOptions = [
|
|
69
|
+
const downloadOptions = [
|
|
70
|
+
FILE_TYPES.CSV,
|
|
71
|
+
FILE_TYPES.TSV,
|
|
72
|
+
FILE_TYPES.JSON,
|
|
73
|
+
FILE_TYPES.PARQUET,
|
|
74
|
+
];
|
|
72
75
|
const copyOptions = [
|
|
73
76
|
FILE_TYPES.TSV,
|
|
74
77
|
FILE_TYPES.JSON,
|
|
@@ -79,6 +82,15 @@ const copyOptions = [
|
|
|
79
82
|
type DownloadFormat = (typeof downloadOptions)[number]["format"];
|
|
80
83
|
type CopyFormat = (typeof copyOptions)[number]["format"];
|
|
81
84
|
|
|
85
|
+
// Each clipboard-copy format fetches from a backend download format, then
|
|
86
|
+
// transforms the payload client-side as needed.
|
|
87
|
+
const COPY_SOURCE_FORMAT: Record<CopyFormat, DownloadFormat> = {
|
|
88
|
+
csv: "csv",
|
|
89
|
+
tsv: "tsv",
|
|
90
|
+
json: "json",
|
|
91
|
+
markdown: "json",
|
|
92
|
+
};
|
|
93
|
+
|
|
82
94
|
export interface ExportActionProps {
|
|
83
95
|
downloadAs: (req: { format: DownloadFormat }) => Promise<{
|
|
84
96
|
url: string;
|
|
@@ -91,6 +103,7 @@ export interface ExportActionProps {
|
|
|
91
103
|
// marimo-lsp inside VS Code) declares a download size cap. Null/undefined
|
|
92
104
|
// means "no info" and the gate stays disabled (fail-open).
|
|
93
105
|
sizeBytes?: number | null;
|
|
106
|
+
sizeBytesIsLoading?: boolean;
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
const labelForDownloadFormat = (format: DownloadFormat): string =>
|
|
@@ -99,14 +112,19 @@ const labelForCopyFormat = (format: CopyFormat): string =>
|
|
|
99
112
|
copyOptions.find((opt) => opt.format === format)?.label ?? format;
|
|
100
113
|
|
|
101
114
|
export const ExportMenu: React.FC<ExportActionProps> = (props) => {
|
|
102
|
-
const
|
|
103
|
-
const [open, setOpen] = React.useState(false);
|
|
115
|
+
const [downloadMenuOpen, setDownloadMenuOpen] = React.useState(false);
|
|
104
116
|
const policy = useAtomValue(downloadSizeLimitAtom);
|
|
105
|
-
const
|
|
117
|
+
const overLimit = !!(
|
|
106
118
|
policy &&
|
|
107
119
|
props.sizeBytes != null &&
|
|
108
120
|
props.sizeBytes > policy.limitBytes
|
|
109
121
|
);
|
|
122
|
+
const disabled = !!(policy && (props.sizeBytesIsLoading || overLimit));
|
|
123
|
+
const tooltipContent = !disabled
|
|
124
|
+
? "Export"
|
|
125
|
+
: props.sizeBytesIsLoading
|
|
126
|
+
? "Checking download size…"
|
|
127
|
+
: policy?.unavailableMessage;
|
|
110
128
|
|
|
111
129
|
const button = (
|
|
112
130
|
<Button
|
|
@@ -116,7 +134,7 @@ export const ExportMenu: React.FC<ExportActionProps> = (props) => {
|
|
|
116
134
|
disabled={disabled}
|
|
117
135
|
className={cn(
|
|
118
136
|
"print:hidden text-xs gap-1",
|
|
119
|
-
|
|
137
|
+
downloadMenuOpen ? "text-primary" : "text-muted-foreground",
|
|
120
138
|
)}
|
|
121
139
|
>
|
|
122
140
|
<DownloadIcon className="w-3.5 h-3.5" />
|
|
@@ -206,7 +224,7 @@ export const ExportMenu: React.FC<ExportActionProps> = (props) => {
|
|
|
206
224
|
await withLoadingToast(
|
|
207
225
|
`Preparing ${labelForCopyFormat(format)} for clipboard...`,
|
|
208
226
|
async () => {
|
|
209
|
-
const sourceFormat
|
|
227
|
+
const sourceFormat = COPY_SOURCE_FORMAT[format];
|
|
210
228
|
const result = await resolveDownloadUrl(sourceFormat, () => {
|
|
211
229
|
void handleClipboardCopy(format);
|
|
212
230
|
});
|
|
@@ -216,19 +234,15 @@ export const ExportMenu: React.FC<ExportActionProps> = (props) => {
|
|
|
216
234
|
|
|
217
235
|
let text: string;
|
|
218
236
|
switch (format) {
|
|
219
|
-
case "tsv":
|
|
220
|
-
|
|
221
|
-
text =
|
|
237
|
+
case "tsv":
|
|
238
|
+
case "csv":
|
|
239
|
+
text = await fetchText(result.url);
|
|
222
240
|
break;
|
|
223
|
-
}
|
|
224
241
|
case "json": {
|
|
225
242
|
const json = await fetchJson(result.url);
|
|
226
243
|
text = JSON.stringify(json, null, 2);
|
|
227
244
|
break;
|
|
228
245
|
}
|
|
229
|
-
case "csv":
|
|
230
|
-
text = await fetchText(result.url);
|
|
231
|
-
break;
|
|
232
246
|
case "markdown": {
|
|
233
247
|
const json = await fetchJson(result.url);
|
|
234
248
|
text = jsonToMarkdown(json);
|
|
@@ -248,10 +262,14 @@ export const ExportMenu: React.FC<ExportActionProps> = (props) => {
|
|
|
248
262
|
};
|
|
249
263
|
|
|
250
264
|
return (
|
|
251
|
-
<DropdownMenu
|
|
265
|
+
<DropdownMenu
|
|
266
|
+
modal={false}
|
|
267
|
+
open={downloadMenuOpen}
|
|
268
|
+
onOpenChange={setDownloadMenuOpen}
|
|
269
|
+
>
|
|
252
270
|
<Tooltip
|
|
253
|
-
content={
|
|
254
|
-
open={
|
|
271
|
+
content={tooltipContent}
|
|
272
|
+
open={downloadMenuOpen ? false : undefined}
|
|
255
273
|
>
|
|
256
274
|
<DropdownMenuTrigger asChild={true} disabled={disabled}>
|
|
257
275
|
<span tabIndex={disabled ? 0 : -1} className="inline-flex">
|