@marimo-team/islands 0.23.7-dev0 → 0.23.7-dev2

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.
@@ -8,7 +8,7 @@ import { t as require_react } from "./react-DA-nE2FX.js";
8
8
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
9
9
  import "./html-to-image-hMMPiNe_.js";
10
10
  import "./chunk-5FQGJX7Z-CO1e63h_.js";
11
- import { At as EyeOff, Pt as Code, a as DEFAULT_SLIDE_TYPE, c as Slide, i as DEFAULT_DECK_TRANSITION, jt as Expand, s as SlideSidebar, t as useNotebookCodeAvailable } from "./code-visibility-B-Q3ImOe.js";
11
+ import { Ft as Code, Mt as Expand, a as DEFAULT_SLIDE_TYPE, c as Slide, i as DEFAULT_DECK_TRANSITION, jt as EyeOff, s as SlideSidebar, t as useNotebookCodeAvailable } from "./code-visibility-BZocwq8O.js";
12
12
  import "./input-BAOe64zx.js";
13
13
  import "./toDate-CHtl9vts.js";
14
14
  import "./react-dom-BWRJ_g_k.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.23.7-dev0",
3
+ "version": "0.23.7-dev2",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -12,7 +12,7 @@ import { useLocale } from "react-aria";
12
12
  import { logNever } from "@/utils/assertNever";
13
13
  import { cn } from "@/utils/cn";
14
14
  import { copyToClipboard } from "@/utils/copy";
15
- import { downloadByURL } from "@/utils/download";
15
+ import { downloadByURL, withLoadingToast } from "@/utils/download";
16
16
  import { prettyError } from "@/utils/errors";
17
17
  import { Filenames } from "@/utils/filenames";
18
18
  import {
@@ -33,17 +33,6 @@ import {
33
33
  import { Tooltip } from "../ui/tooltip";
34
34
  import { toast } from "../ui/use-toast";
35
35
 
36
- type DownloadFormat = "csv" | "json" | "parquet";
37
-
38
- export interface ExportActionProps {
39
- downloadAs: (req: { format: DownloadFormat }) => Promise<{
40
- url: string;
41
- filename: string;
42
- error?: string | null;
43
- missing_packages?: string[] | null;
44
- }>;
45
- }
46
-
47
36
  const FILE_TYPES = {
48
37
  CSV: {
49
38
  label: "CSV",
@@ -84,8 +73,23 @@ const copyOptions = [
84
73
  FILE_TYPES.CSV,
85
74
  FILE_TYPES.MARKDOWN,
86
75
  ];
76
+
77
+ type DownloadFormat = (typeof downloadOptions)[number]["format"];
78
+ type CopyFormat = (typeof copyOptions)[number]["format"];
79
+
80
+ export interface ExportActionProps {
81
+ downloadAs: (req: { format: DownloadFormat }) => Promise<{
82
+ url: string;
83
+ filename: string;
84
+ error?: string | null;
85
+ missing_packages?: string[] | null;
86
+ }>;
87
+ }
88
+
87
89
  const labelForDownloadFormat = (format: DownloadFormat): string =>
88
90
  downloadOptions.find((opt) => opt.format === format)?.label ?? format;
91
+ const labelForCopyFormat = (format: CopyFormat): string =>
92
+ copyOptions.find((opt) => opt.format === format)?.label ?? format;
89
93
 
90
94
  export const ExportMenu: React.FC<ExportActionProps> = (props) => {
91
95
  const { locale } = useLocale();
@@ -143,71 +147,84 @@ export const ExportMenu: React.FC<ExportActionProps> = (props) => {
143
147
  };
144
148
 
145
149
  const handleDownload = async (format: DownloadFormat) => {
146
- const result = await resolveDownloadUrl(format, () => {
147
- void handleDownload(format);
148
- });
149
- if (!result) {
150
- return;
151
- }
152
- const rawName = (result.filename ?? "").trim();
153
- const baseName = Filenames.withoutExtension(rawName) || "download";
154
- const downloadName = `${baseName}.${format}`;
155
- // Append ?download=1 so the server returns Content-Disposition: attachment.
156
- // This forces a save even when <a download> is ignored — e.g., inside
157
- // sandboxed iframes that lack `allow-downloads`. Skip for data: URLs
158
- // (used in pyodide/wasm) since query params would corrupt the payload.
159
- let downloadUrl = result.url;
160
- if (!downloadUrl.startsWith("data:")) {
161
- const separator = downloadUrl.includes("?") ? "&" : "?";
162
- const params = new URLSearchParams({
163
- download: "1",
164
- filename: downloadName,
165
- });
166
- downloadUrl = `${downloadUrl}${separator}${params.toString()}`;
150
+ const label = labelForDownloadFormat(format);
151
+ const ok = await withLoadingToast(
152
+ `Preparing ${label} export...`,
153
+ async () => {
154
+ const result = await resolveDownloadUrl(format, () => {
155
+ void handleDownload(format);
156
+ });
157
+ if (!result) {
158
+ return false;
159
+ }
160
+ const rawName = (result.filename ?? "").trim();
161
+ const baseName = Filenames.withoutExtension(rawName) || "download";
162
+ const downloadName = `${baseName}.${format}`;
163
+ // Append ?download=1 so the server returns Content-Disposition: attachment.
164
+ // This forces a save even when <a download> is ignored — e.g., inside
165
+ // sandboxed iframes that lack `allow-downloads`. Skip for data: URLs
166
+ // (used in pyodide/wasm) since query params would corrupt the payload.
167
+ let downloadUrl = result.url;
168
+ if (!downloadUrl.startsWith("data:")) {
169
+ const separator = downloadUrl.includes("?") ? "&" : "?";
170
+ const params = new URLSearchParams({
171
+ download: "1",
172
+ filename: downloadName,
173
+ });
174
+ downloadUrl = `${downloadUrl}${separator}${params.toString()}`;
175
+ }
176
+ downloadByURL(downloadUrl, downloadName);
177
+ return true;
178
+ },
179
+ );
180
+ if (ok) {
181
+ toast({ title: `${label} download started` });
167
182
  }
168
- downloadByURL(downloadUrl, downloadName);
169
183
  };
170
184
 
171
- const handleClipboardCopy = async (
172
- format: (typeof copyOptions)[number]["format"],
173
- ) => {
174
- const sourceFormat: DownloadFormat = format === "csv" ? "csv" : "json";
175
- const result = await resolveDownloadUrl(sourceFormat, () => {
176
- void handleClipboardCopy(format);
177
- });
178
- if (!result) {
179
- return;
180
- }
185
+ const handleClipboardCopy = async (format: CopyFormat) => {
186
+ await withLoadingToast(
187
+ `Preparing ${labelForCopyFormat(format)} for clipboard...`,
188
+ async () => {
189
+ const sourceFormat: DownloadFormat = format === "csv" ? "csv" : "json";
190
+ const result = await resolveDownloadUrl(sourceFormat, () => {
191
+ void handleClipboardCopy(format);
192
+ });
193
+ if (!result) {
194
+ return;
195
+ }
181
196
 
182
- let text: string;
183
- switch (format) {
184
- case "tsv": {
185
- const json = await fetchJson(result.url);
186
- text = jsonToTSV(json, locale);
187
- break;
188
- }
189
- case "json": {
190
- const json = await fetchJson(result.url);
191
- text = JSON.stringify(json, null, 2);
192
- break;
193
- }
194
- case "csv":
195
- text = await fetchText(result.url);
196
- break;
197
- case "markdown": {
198
- const json = await fetchJson(result.url);
199
- text = jsonToMarkdown(json);
200
- break;
201
- }
202
- default:
203
- logNever(format);
204
- return;
205
- }
197
+ let text: string;
198
+ switch (format) {
199
+ case "tsv": {
200
+ const json = await fetchJson(result.url);
201
+ text = jsonToTSV(json, locale);
202
+ break;
203
+ }
204
+ case "json": {
205
+ const json = await fetchJson(result.url);
206
+ text = JSON.stringify(json, null, 2);
207
+ break;
208
+ }
209
+ case "csv":
210
+ text = await fetchText(result.url);
211
+ break;
212
+ case "markdown": {
213
+ const json = await fetchJson(result.url);
214
+ text = jsonToMarkdown(json);
215
+ break;
216
+ }
217
+ default:
218
+ logNever(format);
219
+ return;
220
+ }
206
221
 
207
- await copyToClipboard(text);
208
- toast({
209
- title: "Copied to clipboard",
210
- });
222
+ await copyToClipboard(text);
223
+ toast({
224
+ title: "Copied to clipboard",
225
+ });
226
+ },
227
+ );
211
228
  };
212
229
 
213
230
  return (