@marimo-team/islands 0.24.1-dev50 → 0.24.1-dev51

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.
@@ -31787,7 +31787,7 @@ function ZG() {
31787
31787
  return i.warn("Failed to get version from mount config"), null;
31788
31788
  }
31789
31789
  }
31790
- var QG = ZG() || "0.24.1-dev50", $G = Ti(QG), eK = Ti(!0);
31790
+ var QG = ZG() || "0.24.1-dev51", $G = Ti(QG), eK = Ti(!0);
31791
31791
  Ti(null);
31792
31792
  //#endregion
31793
31793
  //#region src/core/meta/code-visibility.ts
package/dist/main.js CHANGED
@@ -6,7 +6,7 @@ import { n as x, r as S, t as C } from "./copy-DgAphl4k.js";
6
6
  import { $t as w, At as T, B as E, C as D, Cn as O, Ct as k, Dn as A, Dt as j, En as M, Et as N, F as P, Ft as F, G as I, Gn as L, Gt as ee, H as te, Hn as R, Ht as ne, I as z, In as re, J as ie, Jn as ae, K as oe, Kn as se, Kt as ce, L as le, Ln as ue, Mn as de, Nn as fe, On as pe, Ot as me, P as he, Pn as ge, Pt as _e, Q as ve, Qn as ye, R as be, Rt as xe, Sn as Se, St as Ce, Tt as we, U as Te, Un as Ee, Ut as De, Vt as Oe, W as ke, Wn as Ae, X as je, Y as Me, Yn as Ne, Yt as Pe, Zn as Fe, _ as Ie, _n as Le, _t as Re, a as ze, an as Be, b as Ve, bn as He, bt as Ue, ct as We, en as Ge, g as Ke, gt as qe, hn as Je, i as Ye, in as Xe, it as Ze, j as Qe, jn as $e, jt as et, k as tt, mn as nt, n as rt, nn as it, nt as at, o as ot, ot as st, pn as ct, q as lt, rn as ut, rt as dt, st as ft, tn as pt, un as mt, v as ht, vt as gt, xn as _t, xt as vt, y as yt, yn as bt, yt as xt, z as St, zn as Ct } from "./html-to-image-hKcfSWj_.js";
7
7
  import { o as wt, s as Tt, u as Et } from "./dist-DTSuOdDc.js";
8
8
  import { D as Dt, E as Ot, O as kt, S as At, T as jt, _ as Mt, a as Nt, c as Pt, d as Ft, f as It, g as Lt, h as Rt, i as zt, l as Bt, m as Vt, n as Ht, o as Ut, p as Wt, r as Gt, s as Kt, t as qt, u as Jt, v as Yt, w as Xt, x as Zt, y as Qt } from "./spec-DqUrBQpS.js";
9
- import { $ as $t, $t as en, A as tn, At as nn, B as rn, Bt as an, C as on, Ct as sn, D as cn, Dt as ln, E as un, Et as dn, F as fn, Ft as pn, G as mn, Gt as hn, H as gn, Ht as _n, I as vn, It as yn, Jt as bn, K as xn, Kt as Sn, L as Cn, Lt as wn, M as Tn, Mt as En, N as Dn, Nt as On, O as kn, Ot as An, P as jn, Pt as Mn, Q as Nn, Qt as Pn, R as Fn, Rt as In, S as Ln, St as Rn, T as zn, Tt as Bn, U as Vn, Ut as Hn, V as Un, Vt as Wn, W as Gn, Wt as Kn, Xt as qn, Yt as Jn, Z as Yn, Zt as Xn, _ as Zn, an as Qn, at as $n, b as er, bt as tr, c as nr, cn as rr, ct as ir, d as ar, dn as or, dt as sr, en as cr, et as lr, f as ur, fn as dr, ft as fr, g as pr, gt as mr, h as hr, ht as gr, i as _r, it as vr, j as yr, jt as br, k as xr, kt as Sr, ln as Cr, lt as wr, m as Tr, mt as Er, n as Dr, nn as Or, nt as kr, on as Ar, ot as jr, p as Mr, pn as Nr, pt as Pr, q as Fr, qt as Ir, r as Lr, rn as Rr, rt as zr, sn as Br, st as Vr, t as Hr, tn as Ur, tt as Wr, u as Gr, un as Kr, ut as qr, v as Jr, w as Yr, wt as Xr, x as Zr, xt as Qr, y as $r, yt as ei, z as ti, zt as ni } from "./common-DuhsfU8I.js";
9
+ import { $ as $t, $t as en, A as tn, At as nn, B as rn, Bt as an, C as on, Ct as sn, D as cn, Dt as ln, E as un, Et as dn, F as fn, Ft as pn, G as mn, Gt as hn, H as gn, Ht as _n, I as vn, It as yn, Jt as bn, K as xn, Kt as Sn, L as Cn, Lt as wn, M as Tn, Mt as En, N as Dn, Nt as On, O as kn, Ot as An, P as jn, Pt as Mn, Q as Nn, Qt as Pn, R as Fn, Rt as In, S as Ln, St as Rn, T as zn, Tt as Bn, U as Vn, Ut as Hn, V as Un, Vt as Wn, W as Gn, Wt as Kn, Xt as qn, Yt as Jn, Z as Yn, Zt as Xn, _ as Zn, an as Qn, at as $n, b as er, bt as tr, c as nr, cn as rr, ct as ir, d as ar, dn as or, dt as sr, en as cr, et as lr, f as ur, fn as dr, ft as fr, g as pr, gt as mr, h as hr, ht as gr, i as _r, it as vr, j as yr, jt as br, k as xr, kt as Sr, ln as Cr, lt as wr, m as Tr, mt as Er, n as Dr, nn as Or, nt as kr, on as Ar, ot as jr, p as Mr, pn as Nr, pt as Pr, q as Fr, qt as Ir, r as Lr, rn as Rr, rt as zr, sn as Br, st as Vr, t as Hr, tn as Ur, tt as Wr, u as Gr, un as Kr, ut as qr, v as Jr, w as Yr, wt as Xr, x as Zr, xt as Qr, y as $r, yt as ei, z as ti, zt as ni } from "./common-DTAGJQCY.js";
10
10
  import { c as ri, i as ii, n as ai, o as oi, t as si } from "./useLifecycle-D-zb2Ha5.js";
11
11
  import { t as ci } from "./check-DIJJHY-E.js";
12
12
  import { A as li, C as ui, D as di, E as fi, F as pi, I as mi, M as hi, N as gi, P as _i, R as vi, S as yi, a as bi, b as xi, c as Si, i as Ci, j as wi, l as Ti, n as Ei, o as Di, r as Oi, s as ki, t as Ai, w as ji, x as Mi } from "./select-Bakm9EUs.js";
@@ -35256,7 +35256,7 @@ function Gq(e) {
35256
35256
  }
35257
35257
  //#endregion
35258
35258
  //#region src/components/editor/renderers/slides-layout/slides-layout.tsx
35259
- var Kq = q.lazy(() => import("./reveal-component-AVl0irhz.js")), qq = (e) => {
35259
+ var Kq = q.lazy(() => import("./reveal-component-DJp2KMep.js")), qq = (e) => {
35260
35260
  let t = (0, Y.c)(33), { layout: n, setLayout: r, cells: i, mode: a } = e, o = Nm(k), s = a === "read" || o, c = Nm(lt) > 1, [l, u] = (0, q.useState)(null), d;
35261
35261
  t[0] !== i || t[1] !== n ? (d = UU(i, n), t[0] = i, t[1] = n, t[2] = d) : d = t[2];
35262
35262
  let { slideCells: f, skippedIds: p, noOutputIds: m, slideTypes: h, startCellIndex: g } = d, _;
@@ -4,7 +4,7 @@ import { t as o } from "./react-DMWcHin-.js";
4
4
  import { t as s } from "./compiler-runtime-CAH9RLw2.js";
5
5
  import { Ct as c, rt as l } from "./html-to-image-hKcfSWj_.js";
6
6
  import { u } from "./dist-DTSuOdDc.js";
7
- import { J as d, X as f, Y as p, in as m, l as h, n as g, rn as _, sn as v, t as y, u as b } from "./common-DuhsfU8I.js";
7
+ import { J as d, X as f, Y as p, in as m, l as h, n as g, rn as _, sn as v, t as y, u as b } from "./common-DTAGJQCY.js";
8
8
  import { R as x } from "./select-Bakm9EUs.js";
9
9
  import { X as S } from "./dropdown-menu-DEu759Oe.js";
10
10
  import { t as C } from "./jsx-runtime-CpoKNRgu.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.24.1-dev50",
3
+ "version": "0.24.1-dev51",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -8,7 +8,10 @@ describe("getFileRenderMode", () => {
8
8
  ["text/plain", false, "text"],
9
9
  ["text/x-rust", false, "text"],
10
10
  ["application/json", false, "text"],
11
+ ["application/toml", false, "text"],
12
+ ["application/x-toml", false, "text"],
11
13
  ["application/xml", false, "text"],
14
+ ["text/x-toml", false, "text"],
12
15
  ["text/csv", false, "csv"],
13
16
  ["image/png", true, "media"],
14
17
  ["video/mp4", true, "media"],
@@ -15,13 +15,19 @@ vi.mock("@/plugins/impl/code/LazyAnyLanguageCodeMirror", () => ({
15
15
  LazyAnyLanguageCodeMirror: ({
16
16
  value,
17
17
  onChange,
18
+ language,
19
+ readOnly,
18
20
  }: {
19
21
  value?: string;
20
22
  onChange?: (value: string) => void;
23
+ language?: string;
24
+ readOnly?: boolean;
21
25
  }) => (
22
26
  <textarea
23
27
  data-testid="code-editor"
28
+ data-language={language}
24
29
  value={value ?? ""}
30
+ readOnly={readOnly}
25
31
  onChange={(evt) => onChange?.(evt.target.value)}
26
32
  />
27
33
  ),
@@ -106,6 +112,21 @@ describe("FileViewer bounded previews", () => {
106
112
  expect(screen.queryByText(/too large to preview/i)).not.toBeInTheDocument();
107
113
  });
108
114
 
115
+ it("renders TOML files as editable TOML", async () => {
116
+ renderViewer({
117
+ file: { ...file, name: "pyproject.toml", size: 20 },
118
+ contents: '[project]\nname = "demo"',
119
+ mimeType: "application/toml",
120
+ isBase64: false,
121
+ isTooLarge: false,
122
+ });
123
+
124
+ const editor =
125
+ await screen.findByTestId<HTMLTextAreaElement>("code-editor");
126
+ expect(editor).toHaveAttribute("data-language", "toml");
127
+ expect(editor).not.toHaveAttribute("readonly");
128
+ });
129
+
109
130
  it("preserves edits to an initially empty text file across remount", async () => {
110
131
  const emptyFile: FileInfo = {
111
132
  id: "/workspace/empty.txt",
@@ -0,0 +1,129 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { fireEvent, render, screen } from "@testing-library/react";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { buildMediaSource, FileContentRenderer } from "../renderers";
5
+
6
+ vi.mock("@/plugins/impl/code/LazyAnyLanguageCodeMirror", () => ({
7
+ LazyAnyLanguageCodeMirror: ({
8
+ value,
9
+ language,
10
+ readOnly,
11
+ onChange,
12
+ }: {
13
+ value?: string;
14
+ language?: string;
15
+ readOnly?: boolean;
16
+ onChange?: (value: string) => void;
17
+ }) => (
18
+ <textarea
19
+ data-testid="code-editor"
20
+ data-language={language}
21
+ value={value}
22
+ readOnly={readOnly}
23
+ onChange={(event) => onChange?.(event.target.value)}
24
+ />
25
+ ),
26
+ }));
27
+
28
+ vi.mock("@/theme/useTheme", () => ({
29
+ useTheme: () => ({ theme: "light" }),
30
+ }));
31
+
32
+ describe("buildMediaSource", () => {
33
+ it("returns base64 source for binary media", () => {
34
+ expect(
35
+ buildMediaSource({
36
+ contents: "aGVsbG8=",
37
+ mimeType: "image/png",
38
+ isBase64: true,
39
+ }),
40
+ ).toEqual({
41
+ base64: "aGVsbG8=",
42
+ mime: "image/png",
43
+ });
44
+ });
45
+
46
+ it("returns UTF-8 data URL for text-based media", () => {
47
+ const svg = '<svg xmlns="http://www.w3.org/2000/svg"></svg>';
48
+ expect(
49
+ buildMediaSource({
50
+ contents: svg,
51
+ mimeType: "image/svg+xml",
52
+ isBase64: false,
53
+ }),
54
+ ).toEqual({
55
+ url: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`,
56
+ });
57
+ });
58
+ });
59
+
60
+ describe("FileContentRenderer", () => {
61
+ it("renders editable TOML with syntax highlighting", () => {
62
+ const onChange = vi.fn();
63
+ render(
64
+ <FileContentRenderer
65
+ mimeType="application/toml"
66
+ contents={'name = "demo"'}
67
+ readOnly={false}
68
+ onChange={onChange}
69
+ />,
70
+ );
71
+
72
+ const editor = screen.getByTestId<HTMLTextAreaElement>("code-editor");
73
+ expect(editor).toHaveAttribute("data-language", "toml");
74
+ expect(editor).not.toHaveAttribute("readonly");
75
+
76
+ fireEvent.change(editor, { target: { value: 'name = "updated"' } });
77
+ expect(onChange).toHaveBeenCalledWith('name = "updated"');
78
+ });
79
+
80
+ it.each(["application/toml", "application/x-toml", "text/x-toml"])(
81
+ "renders %s as read-only TOML by default",
82
+ (mimeType) => {
83
+ render(
84
+ <FileContentRenderer mimeType={mimeType} contents={'name = "demo"'} />,
85
+ );
86
+
87
+ const editor = screen.getByTestId<HTMLTextAreaElement>("code-editor");
88
+ expect(editor).toHaveAttribute("data-language", "toml");
89
+ expect(editor).toHaveAttribute("readonly");
90
+ },
91
+ );
92
+
93
+ it.each([
94
+ "application/octet-stream",
95
+ "text/plain",
96
+ "text/plain; charset=utf-8",
97
+ ])("uses the filename when a provider returns %s", (mimeType) => {
98
+ render(
99
+ <FileContentRenderer
100
+ mimeType={mimeType}
101
+ filename="pyproject.TOML"
102
+ contents={'name = "demo"'}
103
+ />,
104
+ );
105
+
106
+ expect(screen.getByTestId("code-editor")).toHaveAttribute(
107
+ "data-language",
108
+ "toml",
109
+ );
110
+ });
111
+
112
+ it.each(["file.constructor", "file.__proto__"])(
113
+ "ignores inherited extension-map property in %s",
114
+ (filename) => {
115
+ render(
116
+ <FileContentRenderer
117
+ mimeType="text/plain"
118
+ filename={filename}
119
+ contents="text"
120
+ />,
121
+ );
122
+
123
+ expect(screen.getByTestId("code-editor")).toHaveAttribute(
124
+ "data-language",
125
+ "markdown",
126
+ );
127
+ },
128
+ );
129
+ });
@@ -240,6 +240,7 @@ export const FileViewer: React.FC<Props> = ({ file, onOpenNotebook }) => {
240
240
  {warningBanner}
241
241
  <FileContentRenderer
242
242
  mimeType={mimeType}
243
+ filename={data.file.name}
243
244
  contents={
244
245
  isText
245
246
  ? internalValue
@@ -16,6 +16,7 @@ import { useTheme } from "@/theme/useTheme";
16
16
  import { Arrays } from "@/utils/arrays";
17
17
  import { type Base64String, base64ToDataURL } from "@/utils/json/base64";
18
18
  import { Objects } from "@/utils/objects";
19
+ import { Paths } from "@/utils/paths";
19
20
 
20
21
  const PAGE_SIZE = 25;
21
22
 
@@ -38,13 +39,36 @@ export const MIME_TO_LANGUAGE: Record<string, string> = {
38
39
  "text/css": "css",
39
40
  "text/x-python": "python",
40
41
  "application/json": "json",
42
+ "application/toml": "toml",
43
+ "application/x-toml": "toml",
41
44
  "application/xml": "xml",
45
+ "text/x-toml": "toml",
42
46
  "text/x-yaml": "yaml",
43
47
  "text/csv": "markdown",
44
48
  "text/plain": "markdown",
45
49
  default: "markdown",
46
50
  };
47
51
 
52
+ const EXTENSION_TO_LANGUAGE: Record<string, string> = {
53
+ toml: "toml",
54
+ };
55
+
56
+ const GENERIC_MIME_TYPES = new Set(["application/octet-stream", "text/plain"]);
57
+
58
+ function resolveLanguage(mimeType: string, filename?: string): string {
59
+ const normalizedMimeType = mimeType.split(";", 1)[0].trim().toLowerCase();
60
+ if (GENERIC_MIME_TYPES.has(normalizedMimeType) && filename) {
61
+ const extension = Paths.extension(filename).toLowerCase();
62
+ const language = Object.hasOwn(EXTENSION_TO_LANGUAGE, extension)
63
+ ? EXTENSION_TO_LANGUAGE[extension]
64
+ : undefined;
65
+ if (language) {
66
+ return language;
67
+ }
68
+ }
69
+ return MIME_TO_LANGUAGE[normalizedMimeType] || MIME_TO_LANGUAGE.default;
70
+ }
71
+
48
72
  /**
49
73
  * Media viewer props: provide either a direct `url` or `base64` + `mime`.
50
74
  * When `url` is set it takes precedence.
@@ -167,6 +191,7 @@ export const MediaRenderer: React.FC<MediaSource & { mimeType: string }> = ({
167
191
  */
168
192
  export const FileContentRenderer: React.FC<{
169
193
  mimeType: string;
194
+ filename?: string;
170
195
  /** Text content for text/CSV files. */
171
196
  contents?: string;
172
197
  /** Media source for image/audio/video/PDF files. */
@@ -177,6 +202,7 @@ export const FileContentRenderer: React.FC<{
177
202
  extensions?: Extension[];
178
203
  }> = ({
179
204
  mimeType,
205
+ filename,
180
206
  contents,
181
207
  mediaSource,
182
208
  readOnly = true,
@@ -202,7 +228,7 @@ export const FileContentRenderer: React.FC<{
202
228
  }
203
229
 
204
230
  if (contents != null) {
205
- const language = MIME_TO_LANGUAGE[mimeType] || MIME_TO_LANGUAGE.default;
231
+ const language = resolveLanguage(mimeType, filename);
206
232
  return (
207
233
  <div className="flex-1 overflow-auto">
208
234
  <Suspense>
@@ -0,0 +1,84 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { render, screen } from "@testing-library/react";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
5
+ import { TooltipProvider } from "@/components/ui/tooltip";
6
+ import { DownloadStorage } from "@/core/storage/request-registry";
7
+ import { StorageFileViewer } from "../storage-file-viewer";
8
+
9
+ vi.mock("@/components/editor/cell/useAddCell", () => ({
10
+ useAddCodeToNewCell: () => vi.fn(),
11
+ }));
12
+
13
+ vi.mock("@/plugins/impl/code/LazyAnyLanguageCodeMirror", () => ({
14
+ LazyAnyLanguageCodeMirror: ({
15
+ value,
16
+ language,
17
+ readOnly,
18
+ }: {
19
+ value?: string;
20
+ language?: string;
21
+ readOnly?: boolean;
22
+ }) => (
23
+ <textarea
24
+ data-testid="code-editor"
25
+ data-language={language}
26
+ value={value}
27
+ readOnly={readOnly}
28
+ />
29
+ ),
30
+ }));
31
+
32
+ vi.mock("@/theme/useTheme", () => ({
33
+ useTheme: () => ({ theme: "light" }),
34
+ }));
35
+
36
+ describe("StorageFileViewer", () => {
37
+ afterEach(() => {
38
+ vi.restoreAllMocks();
39
+ vi.unstubAllGlobals();
40
+ });
41
+
42
+ it("renders TOML from generic storage metadata as read-only TOML", async () => {
43
+ const request = vi.spyOn(DownloadStorage, "request").mockResolvedValue({
44
+ request_id: "request-id",
45
+ url: "https://example.com/pyproject.toml?signature=secret",
46
+ });
47
+ vi.stubGlobal(
48
+ "fetch",
49
+ vi.fn().mockResolvedValue({
50
+ ok: true,
51
+ text: async () => '[project]\nname = "demo"',
52
+ }),
53
+ );
54
+
55
+ render(
56
+ <TooltipProvider>
57
+ <StorageFileViewer
58
+ entry={{
59
+ path: "config/pyproject.toml",
60
+ kind: "file",
61
+ size: 24,
62
+ lastModified: null,
63
+ mimeType: null,
64
+ }}
65
+ namespace="files"
66
+ protocol="s3"
67
+ backendType="fsspec"
68
+ onBack={vi.fn()}
69
+ />
70
+ </TooltipProvider>,
71
+ );
72
+
73
+ const editor =
74
+ await screen.findByTestId<HTMLTextAreaElement>("code-editor");
75
+ expect(editor).toHaveAttribute("data-language", "toml");
76
+ expect(editor).toHaveAttribute("readonly");
77
+ expect(editor).toHaveValue('[project]\nname = "demo"');
78
+ expect(request).toHaveBeenCalledWith({
79
+ namespace: "files",
80
+ path: "config/pyproject.toml",
81
+ preview: true,
82
+ });
83
+ });
84
+ });
@@ -237,6 +237,7 @@ export const StorageFileViewer: React.FC<Props> = ({
237
237
  {renderMetadata({})}
238
238
  <FileContentRenderer
239
239
  mimeType={mime}
240
+ filename={name}
240
241
  contents={preview.type === "text" ? preview.content : undefined}
241
242
  mediaSource={
242
243
  preview.type === "media" ? { url: preview.url } : undefined
@@ -1,31 +0,0 @@
1
- /* Copyright 2026 Marimo. All rights reserved. */
2
- import { describe, expect, it } from "vitest";
3
- import { buildMediaSource } from "../renderers";
4
-
5
- describe("buildMediaSource", () => {
6
- it("returns base64 source for binary media", () => {
7
- expect(
8
- buildMediaSource({
9
- contents: "aGVsbG8=",
10
- mimeType: "image/png",
11
- isBase64: true,
12
- }),
13
- ).toEqual({
14
- base64: "aGVsbG8=",
15
- mime: "image/png",
16
- });
17
- });
18
-
19
- it("returns UTF-8 data URL for text-based media", () => {
20
- const svg = '<svg xmlns="http://www.w3.org/2000/svg"></svg>';
21
- expect(
22
- buildMediaSource({
23
- contents: svg,
24
- mimeType: "image/svg+xml",
25
- isBase64: false,
26
- }),
27
- ).toEqual({
28
- url: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`,
29
- });
30
- });
31
- });