@marimo-team/islands 0.19.5-dev2 → 0.19.5-dev21
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-D5KcOOzu.js → ConnectedDataExplorerComponent-DjQ_E5BA.js} +3 -3
- package/dist/assets/__vite-browser-external-DRa9CT_O.js +1 -0
- package/dist/assets/{worker-BR7KVExK.js → worker-SqntmiwV.js} +2 -2
- package/dist/{glide-data-editor-DsVDCmV2.js → glide-data-editor-zEomQJ3U.js} +2 -2
- package/dist/main.js +39 -22
- package/dist/{mermaid-DZjjc-kI.js → mermaid-D7wtYc6C.js} +2 -2
- package/dist/{spec-B1PGDiGh.js → spec-Cif4tBMJ.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/{types-CbQF8CBX.js → types-BQOP2pRy.js} +1 -1
- package/dist/{useAsyncData-TLXJC7yx.js → useAsyncData-kqbhbSuf.js} +1 -1
- package/dist/{useDeepCompareMemoize-DVnEG7jx.js → useDeepCompareMemoize-B2QEm3jo.js} +1 -1
- package/dist/{useTheme-BllQjRdW.js → useTheme-CVr6Gb_R.js} +4 -1
- package/dist/{vega-component-B2QrGnW8.js → vega-component-DAeU1_cV.js} +3 -3
- package/package.json +1 -1
- package/src/components/app-config/user-config-form.tsx +1 -1
- package/src/components/editor/controls/Controls.tsx +1 -8
- package/src/components/editor/file-tree/file-explorer.tsx +4 -2
- package/src/components/editor/file-tree/file-viewer.tsx +9 -6
- package/src/core/config/config-schema.ts +5 -1
- package/src/core/config/config.ts +4 -0
- package/src/core/lsp/__tests__/transport.test.ts +149 -0
- package/src/core/lsp/transport.ts +48 -0
- package/src/css/app/Cell.css +0 -2
- package/src/plugins/layout/TexPlugin.tsx +7 -5
- package/dist/assets/__vite-browser-external-CgHmDpAZ.js +0 -1
|
@@ -13,7 +13,7 @@ import { c as useComposedRefs, l as Events, s as composeRefs, t as Button, u as
|
|
|
13
13
|
import { s as Logger } from "./hotkeys-C4e3s3sJ.js";
|
|
14
14
|
import { t as require_jsx_runtime } from "./jsx-runtime-CTBg5pdT.js";
|
|
15
15
|
import { t as require_react_dom } from "./react-dom-DJW8xUDg.js";
|
|
16
|
-
import { h as useEvent_default } from "./useTheme-
|
|
16
|
+
import { h as useEvent_default } from "./useTheme-CVr6Gb_R.js";
|
|
17
17
|
import { t as toString_default } from "./toString-C4TLO6FA.js";
|
|
18
18
|
import { i as debounce_default, n as Constants } from "./constants-DuN_eoAL.js";
|
|
19
19
|
import { t as memoizeLastValue } from "./once-C-NkXwWJ.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
2
2
|
import { t as require_react } from "./react-DdA8EBol.js";
|
|
3
3
|
import { t as require_compiler_runtime } from "./compiler-runtime-DHFVbq0b.js";
|
|
4
|
-
import { h as useEvent_default } from "./useTheme-
|
|
4
|
+
import { h as useEvent_default } from "./useTheme-CVr6Gb_R.js";
|
|
5
5
|
import { t as invariant } from "./invariant-BW72tHBT.js";
|
|
6
6
|
var import_compiler_runtime = require_compiler_runtime(), import_react = /* @__PURE__ */ __toESM(require_react(), 1), Result = {
|
|
7
7
|
error(e, s) {
|
|
@@ -8,7 +8,7 @@ import { N as createLucideIcon } from "./Combination-BOmAhdTT.js";
|
|
|
8
8
|
import { a as cva, u as cn } from "./button-D6ZIdUA3.js";
|
|
9
9
|
import { s as Logger } from "./hotkeys-C4e3s3sJ.js";
|
|
10
10
|
import { t as require_jsx_runtime } from "./jsx-runtime-CTBg5pdT.js";
|
|
11
|
-
import { f as waitFor, p as isIslands, u as store, x as atom } from "./useTheme-
|
|
11
|
+
import { f as waitFor, p as isIslands, u as store, x as atom } from "./useTheme-CVr6Gb_R.js";
|
|
12
12
|
import { r as KnownQueryParams } from "./constants-DuN_eoAL.js";
|
|
13
13
|
import { i as tableFromIPC } from "./loader-DH7xXi-E.js";
|
|
14
14
|
var CircleQuestionMark = createLucideIcon("circle-question-mark", [
|
|
@@ -598,7 +598,7 @@ const UserConfigSchema = looseObject({
|
|
|
598
598
|
markdown: boolean().optional(),
|
|
599
599
|
rtc: boolean().optional()
|
|
600
600
|
}).prefault(() => ({})),
|
|
601
|
-
server: looseObject({}).prefault(() => ({})),
|
|
601
|
+
server: looseObject({ disable_file_downloads: boolean().optional() }).prefault(() => ({})),
|
|
602
602
|
diagnostics: looseObject({
|
|
603
603
|
enabled: boolean().optional(),
|
|
604
604
|
sql_linter: boolean().optional()
|
|
@@ -681,6 +681,9 @@ atom((e) => e(appConfigAtom).width), atom((e) => {
|
|
|
681
681
|
var _a, _b;
|
|
682
682
|
let k = e(resolvedMarimoConfigAtom), A = ((_a = k.snippets) == null ? void 0 : _a.custom_paths) ?? [], j = (_b = k.snippets) == null ? void 0 : _b.include_default_snippets;
|
|
683
683
|
return A.length > 0 || j === true;
|
|
684
|
+
}), atom((e) => {
|
|
685
|
+
var _a;
|
|
686
|
+
return ((_a = e(resolvedMarimoConfigAtom).server) == null ? void 0 : _a.disable_file_downloads) ?? false;
|
|
684
687
|
});
|
|
685
688
|
var import_compiler_runtime = require_compiler_runtime(), themeAtom = atom((e) => {
|
|
686
689
|
if (isIslands()) {
|
|
@@ -2,18 +2,18 @@ import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
|
2
2
|
import { t as require_react } from "./react-DdA8EBol.js";
|
|
3
3
|
import { t as require_compiler_runtime } from "./compiler-runtime-DHFVbq0b.js";
|
|
4
4
|
import "./Combination-BOmAhdTT.js";
|
|
5
|
-
import { S as CircleQuestionMark, a as AlertTitle, m as asRemoteURL, n as arrow, o as isValid, r as Alert, t as useDeepCompareMemoize } from "./useDeepCompareMemoize-
|
|
5
|
+
import { S as CircleQuestionMark, a as AlertTitle, m as asRemoteURL, n as arrow, o as isValid, r as Alert, t as useDeepCompareMemoize } from "./useDeepCompareMemoize-B2QEm3jo.js";
|
|
6
6
|
import { l as Events } from "./button-D6ZIdUA3.js";
|
|
7
7
|
import { o as Objects, s as Logger } from "./hotkeys-C4e3s3sJ.js";
|
|
8
8
|
import { t as require_jsx_runtime } from "./jsx-runtime-CTBg5pdT.js";
|
|
9
9
|
import "./react-dom-DJW8xUDg.js";
|
|
10
10
|
import { l as Tooltip, n as ErrorBanner } from "./error-banner-BhqH4mGj.js";
|
|
11
|
-
import { h as useEvent_default, n as useTheme } from "./useTheme-
|
|
11
|
+
import { h as useEvent_default, n as useTheme } from "./useTheme-CVr6Gb_R.js";
|
|
12
12
|
import { t as _baseUniq_default } from "./_baseUniq-BUGws47x.js";
|
|
13
13
|
import { i as debounce_default } from "./constants-DuN_eoAL.js";
|
|
14
14
|
import { a as tooltipHandler, n as vegaLoadData } from "./loader-DH7xXi-E.js";
|
|
15
15
|
import { n as formats } from "./vega-loader.browser-nTy1NZD3.js";
|
|
16
|
-
import { t as useAsyncData } from "./useAsyncData-
|
|
16
|
+
import { t as useAsyncData } from "./useAsyncData-kqbhbSuf.js";
|
|
17
17
|
import { t as j } from "./react-vega-z40f-dXy.js";
|
|
18
18
|
import "./defaultLocale-DZtxSCkJ.js";
|
|
19
19
|
import "./defaultLocale-DMZFeDB8.js";
|
package/package.json
CHANGED
|
@@ -993,7 +993,7 @@ export const UserConfigForm: React.FC = () => {
|
|
|
993
993
|
<br />
|
|
994
994
|
<br />
|
|
995
995
|
Running marimo in a{" "}
|
|
996
|
-
<ExternalLink href="https://docs.marimo.io/guides/
|
|
996
|
+
<ExternalLink href="https://docs.marimo.io/guides/package_management/inlining_dependencies.html">
|
|
997
997
|
sandboxed environment
|
|
998
998
|
</ExternalLink>{" "}
|
|
999
999
|
is only supported by <Kbd className="inline">uv</Kbd>
|
|
@@ -54,9 +54,7 @@ export const Controls = ({
|
|
|
54
54
|
onRun,
|
|
55
55
|
connectionState,
|
|
56
56
|
running,
|
|
57
|
-
appConfig,
|
|
58
57
|
}: ControlsProps): JSX.Element => {
|
|
59
|
-
const appWidth = appConfig.width;
|
|
60
58
|
const undoAvailable = useAtomValue(canUndoDeletesAtom);
|
|
61
59
|
const needsRun = useAtomValue(needsRunAtom);
|
|
62
60
|
const { undoDeleteCell } = useCellActions();
|
|
@@ -103,12 +101,7 @@ export const Controls = ({
|
|
|
103
101
|
</div>
|
|
104
102
|
)}
|
|
105
103
|
|
|
106
|
-
<div
|
|
107
|
-
className={cn(
|
|
108
|
-
bottomRightControls,
|
|
109
|
-
appWidth === "compact" && "xl:flex-row items-end",
|
|
110
|
-
)}
|
|
111
|
-
>
|
|
104
|
+
<div className={cn(bottomRightControls)}>
|
|
112
105
|
<HideInKioskMode>
|
|
113
106
|
<SaveComponent kioskMode={false} />
|
|
114
107
|
</HideInKioskMode>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import { useAtom } from "jotai";
|
|
3
|
+
import { useAtom, useAtomValue } from "jotai";
|
|
4
4
|
import { atomWithStorage } from "jotai/utils";
|
|
5
5
|
import {
|
|
6
6
|
ArrowLeftIcon,
|
|
@@ -47,6 +47,7 @@ import { Tooltip } from "@/components/ui/tooltip";
|
|
|
47
47
|
import { toast } from "@/components/ui/use-toast";
|
|
48
48
|
import { useCellActions } from "@/core/cells/cells";
|
|
49
49
|
import { useLastFocusedCellId } from "@/core/cells/focus";
|
|
50
|
+
import { disableFileDownloadsAtom } from "@/core/config/config";
|
|
50
51
|
import { useRequestClient } from "@/core/network/requests";
|
|
51
52
|
import type { FileInfo } from "@/core/network/types";
|
|
52
53
|
import { isWasm } from "@/core/wasm/utils";
|
|
@@ -396,6 +397,7 @@ const Edit = ({ node }: { node: NodeApi<FileInfo> }) => {
|
|
|
396
397
|
const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
|
|
397
398
|
const { openFile, sendCreateFileOrFolder, sendFileDetails } =
|
|
398
399
|
useRequestClient();
|
|
400
|
+
const disableFileDownloads = useAtomValue(disableFileDownloadsAtom);
|
|
399
401
|
|
|
400
402
|
const fileType: FileType = node.data.isDirectory
|
|
401
403
|
? "directory"
|
|
@@ -611,7 +613,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
|
|
|
611
613
|
</>
|
|
612
614
|
)}
|
|
613
615
|
<DropdownMenuSeparator />
|
|
614
|
-
{!node.data.isDirectory && (
|
|
616
|
+
{!node.data.isDirectory && !disableFileDownloads && (
|
|
615
617
|
<>
|
|
616
618
|
<DropdownMenuItem
|
|
617
619
|
onSelect={async () => {
|
|
@@ -15,7 +15,7 @@ import { Suspense, useEffect, useRef, useState } from "react";
|
|
|
15
15
|
import { renderShortcut } from "@/components/shortcuts/renderShortcut";
|
|
16
16
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
17
17
|
import { Tooltip } from "@/components/ui/tooltip";
|
|
18
|
-
import { hotkeysAtom } from "@/core/config/config";
|
|
18
|
+
import { disableFileDownloadsAtom, hotkeysAtom } from "@/core/config/config";
|
|
19
19
|
import { useRequestClient } from "@/core/network/requests";
|
|
20
20
|
import type { FileInfo } from "@/core/network/types";
|
|
21
21
|
import { filenameAtom } from "@/core/saving/file-state";
|
|
@@ -49,6 +49,7 @@ export const FileViewer: React.FC<Props> = ({ file, onOpenNotebook }) => {
|
|
|
49
49
|
const { theme } = useTheme();
|
|
50
50
|
const { sendFileDetails, sendUpdateFile } = useRequestClient();
|
|
51
51
|
const hotkeys = useAtomValue(hotkeysAtom);
|
|
52
|
+
const disableFileDownloads = useAtomValue(disableFileDownloadsAtom);
|
|
52
53
|
const currentNotebookFilename = useAtomValue(filenameAtom);
|
|
53
54
|
// undefined value means not modified yet
|
|
54
55
|
const [internalValue, setInternalValue] = useState<string>("");
|
|
@@ -152,11 +153,13 @@ export const FileViewer: React.FC<Props> = ({ file, onOpenNotebook }) => {
|
|
|
152
153
|
</Button>
|
|
153
154
|
</Tooltip>
|
|
154
155
|
)}
|
|
155
|
-
|
|
156
|
-
<
|
|
157
|
-
<
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
{!disableFileDownloads && (
|
|
157
|
+
<Tooltip content="Download">
|
|
158
|
+
<Button size="small" onClick={handleDownload}>
|
|
159
|
+
<DownloadIcon />
|
|
160
|
+
</Button>
|
|
161
|
+
</Tooltip>
|
|
162
|
+
)}
|
|
160
163
|
{!isMedia(mimeType) && (
|
|
161
164
|
<>
|
|
162
165
|
<Tooltip content="Copy contents to clipboard">
|
|
@@ -190,7 +190,11 @@ export const UserConfigSchema = z
|
|
|
190
190
|
})
|
|
191
191
|
// Pass through so that we don't remove any extra keys that the user has added.
|
|
192
192
|
.prefault(() => ({})),
|
|
193
|
-
server: z
|
|
193
|
+
server: z
|
|
194
|
+
.looseObject({
|
|
195
|
+
disable_file_downloads: z.boolean().optional(),
|
|
196
|
+
})
|
|
197
|
+
.prefault(() => ({})),
|
|
194
198
|
diagnostics: z
|
|
195
199
|
.looseObject({
|
|
196
200
|
enabled: z.boolean().optional(),
|
|
@@ -125,3 +125,7 @@ export const snippetsEnabledAtom = atom<boolean>((get) => {
|
|
|
125
125
|
const includeDefaultSnippets = config.snippets?.include_default_snippets;
|
|
126
126
|
return customPaths.length > 0 || includeDefaultSnippets === true;
|
|
127
127
|
});
|
|
128
|
+
|
|
129
|
+
export const disableFileDownloadsAtom = atom<boolean>((get) => {
|
|
130
|
+
return get(resolvedMarimoConfigAtom).server?.disable_file_downloads ?? false;
|
|
131
|
+
});
|
|
@@ -41,6 +41,8 @@ describe("ReconnectingWebSocketTransport", () => {
|
|
|
41
41
|
this.connect = vi.fn().mockResolvedValue(undefined);
|
|
42
42
|
this.close = vi.fn();
|
|
43
43
|
this.sendData = vi.fn().mockResolvedValue({ result: "success" });
|
|
44
|
+
this.subscribe = vi.fn();
|
|
45
|
+
this.unsubscribe = vi.fn();
|
|
44
46
|
});
|
|
45
47
|
});
|
|
46
48
|
|
|
@@ -287,4 +289,151 @@ describe("ReconnectingWebSocketTransport", () => {
|
|
|
287
289
|
"Reconnect callback failed",
|
|
288
290
|
);
|
|
289
291
|
});
|
|
292
|
+
|
|
293
|
+
describe("subscribe", () => {
|
|
294
|
+
it("should track subscriptions", () => {
|
|
295
|
+
const getWsUrl = vi.fn(() => mockWsUrl);
|
|
296
|
+
const transport = new ReconnectingWebSocketTransport({ getWsUrl });
|
|
297
|
+
|
|
298
|
+
const handler = vi.fn();
|
|
299
|
+
transport.subscribe("notification", handler);
|
|
300
|
+
|
|
301
|
+
expect((transport as any).pendingSubscriptions).toHaveLength(1);
|
|
302
|
+
expect((transport as any).pendingSubscriptions[0]).toEqual({
|
|
303
|
+
event: "notification",
|
|
304
|
+
handler,
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("should register handler on delegate if it exists", async () => {
|
|
309
|
+
const getWsUrl = vi.fn(() => mockWsUrl);
|
|
310
|
+
const transport = new ReconnectingWebSocketTransport({ getWsUrl });
|
|
311
|
+
|
|
312
|
+
await transport.connect();
|
|
313
|
+
|
|
314
|
+
const handler = vi.fn();
|
|
315
|
+
transport.subscribe("notification", handler);
|
|
316
|
+
|
|
317
|
+
const delegate = (transport as any).delegate;
|
|
318
|
+
expect(delegate.subscribe).toHaveBeenCalledWith("notification", handler);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("should register pending subscriptions when delegate is created", async () => {
|
|
322
|
+
const getWsUrl = vi.fn(() => mockWsUrl);
|
|
323
|
+
const transport = new ReconnectingWebSocketTransport({ getWsUrl });
|
|
324
|
+
|
|
325
|
+
const handler1 = vi.fn();
|
|
326
|
+
const handler2 = vi.fn();
|
|
327
|
+
transport.subscribe("notification", handler1);
|
|
328
|
+
transport.subscribe("response", handler2);
|
|
329
|
+
|
|
330
|
+
await transport.connect();
|
|
331
|
+
|
|
332
|
+
const delegate = (transport as any).delegate;
|
|
333
|
+
expect(delegate.subscribe).toHaveBeenCalledWith("notification", handler1);
|
|
334
|
+
expect(delegate.subscribe).toHaveBeenCalledWith("response", handler2);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("should re-register subscriptions on reconnection", async () => {
|
|
338
|
+
const getWsUrl = vi.fn(() => mockWsUrl);
|
|
339
|
+
const transport = new ReconnectingWebSocketTransport({ getWsUrl });
|
|
340
|
+
|
|
341
|
+
// Add subscription before connection
|
|
342
|
+
const handler = vi.fn();
|
|
343
|
+
transport.subscribe("notification", handler);
|
|
344
|
+
|
|
345
|
+
// First connection
|
|
346
|
+
await transport.connect();
|
|
347
|
+
const firstDelegate = (transport as any).delegate;
|
|
348
|
+
expect(firstDelegate.subscribe).toHaveBeenCalledWith(
|
|
349
|
+
"notification",
|
|
350
|
+
handler,
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// Clear mock calls
|
|
354
|
+
firstDelegate.subscribe.mockClear();
|
|
355
|
+
|
|
356
|
+
// Simulate connection loss
|
|
357
|
+
mockConnection.readyState = WebSocket.CLOSED;
|
|
358
|
+
|
|
359
|
+
// Reconnect by sending data
|
|
360
|
+
const data: any = { method: "test", params: [] };
|
|
361
|
+
await transport.sendData(data, 5000);
|
|
362
|
+
|
|
363
|
+
// New delegate should have been created
|
|
364
|
+
const secondDelegate = (transport as any).delegate;
|
|
365
|
+
expect(secondDelegate).not.toBe(firstDelegate);
|
|
366
|
+
|
|
367
|
+
// Subscription should be re-registered on new delegate
|
|
368
|
+
expect(secondDelegate.subscribe).toHaveBeenCalledWith(
|
|
369
|
+
"notification",
|
|
370
|
+
handler,
|
|
371
|
+
);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
describe("unsubscribe", () => {
|
|
376
|
+
it("should remove subscription from tracking", () => {
|
|
377
|
+
const getWsUrl = vi.fn(() => mockWsUrl);
|
|
378
|
+
const transport = new ReconnectingWebSocketTransport({ getWsUrl });
|
|
379
|
+
|
|
380
|
+
const handler = vi.fn();
|
|
381
|
+
transport.subscribe("notification", handler);
|
|
382
|
+
expect((transport as any).pendingSubscriptions).toHaveLength(1);
|
|
383
|
+
|
|
384
|
+
transport.unsubscribe("notification", handler);
|
|
385
|
+
expect((transport as any).pendingSubscriptions).toHaveLength(0);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("should unregister from delegate if it exists", async () => {
|
|
389
|
+
const getWsUrl = vi.fn(() => mockWsUrl);
|
|
390
|
+
const transport = new ReconnectingWebSocketTransport({ getWsUrl });
|
|
391
|
+
|
|
392
|
+
await transport.connect();
|
|
393
|
+
|
|
394
|
+
const handler = vi.fn();
|
|
395
|
+
transport.subscribe("notification", handler);
|
|
396
|
+
|
|
397
|
+
const delegate = (transport as any).delegate;
|
|
398
|
+
delegate.unsubscribe.mockClear();
|
|
399
|
+
|
|
400
|
+
transport.unsubscribe("notification", handler);
|
|
401
|
+
|
|
402
|
+
expect(delegate.unsubscribe).toHaveBeenCalledWith(
|
|
403
|
+
"notification",
|
|
404
|
+
handler,
|
|
405
|
+
);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it("should not re-register unsubscribed handlers on reconnection", async () => {
|
|
409
|
+
const getWsUrl = vi.fn(() => mockWsUrl);
|
|
410
|
+
const transport = new ReconnectingWebSocketTransport({ getWsUrl });
|
|
411
|
+
|
|
412
|
+
const handler1 = vi.fn();
|
|
413
|
+
const handler2 = vi.fn();
|
|
414
|
+
transport.subscribe("notification", handler1);
|
|
415
|
+
transport.subscribe("response", handler2);
|
|
416
|
+
|
|
417
|
+
await transport.connect();
|
|
418
|
+
|
|
419
|
+
// Unsubscribe handler1
|
|
420
|
+
transport.unsubscribe("notification", handler1);
|
|
421
|
+
|
|
422
|
+
// Simulate connection loss
|
|
423
|
+
mockConnection.readyState = WebSocket.CLOSED;
|
|
424
|
+
|
|
425
|
+
// Reconnect by sending data
|
|
426
|
+
const data: any = { method: "test", params: [] };
|
|
427
|
+
await transport.sendData(data, 5000);
|
|
428
|
+
|
|
429
|
+
const newDelegate = (transport as any).delegate;
|
|
430
|
+
|
|
431
|
+
// Only handler2 should be registered on the new delegate
|
|
432
|
+
expect(newDelegate.subscribe).not.toHaveBeenCalledWith(
|
|
433
|
+
"notification",
|
|
434
|
+
handler1,
|
|
435
|
+
);
|
|
436
|
+
expect(newDelegate.subscribe).toHaveBeenCalledWith("response", handler2);
|
|
437
|
+
});
|
|
438
|
+
});
|
|
290
439
|
});
|
|
@@ -23,6 +23,11 @@ export interface ReconnectingWebSocketTransportOptions {
|
|
|
23
23
|
onReconnect?: () => Promise<void>;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
interface Subscription {
|
|
27
|
+
event: "pending" | "notification" | "response" | "error";
|
|
28
|
+
handler: Parameters<Transport["subscribe"]>[1];
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
/**
|
|
27
32
|
* A WebSocket transport that automatically reconnects when the connection is lost.
|
|
28
33
|
* This handles cases like computer sleep/wake or network interruptions.
|
|
@@ -33,6 +38,7 @@ export class ReconnectingWebSocketTransport extends Transport {
|
|
|
33
38
|
private connectionPromise: Promise<void> | undefined;
|
|
34
39
|
private isClosed = false;
|
|
35
40
|
private hasConnectedBefore = false;
|
|
41
|
+
private pendingSubscriptions: Subscription[] = [];
|
|
36
42
|
|
|
37
43
|
constructor(options: ReconnectingWebSocketTransportOptions) {
|
|
38
44
|
super();
|
|
@@ -55,6 +61,12 @@ export class ReconnectingWebSocketTransport extends Transport {
|
|
|
55
61
|
|
|
56
62
|
// Create a new delegate
|
|
57
63
|
this.delegate = new WebSocketTransport(this.options.getWsUrl());
|
|
64
|
+
|
|
65
|
+
// Re-register all pending subscriptions on the new delegate
|
|
66
|
+
for (const { event, handler } of this.pendingSubscriptions) {
|
|
67
|
+
this.delegate.subscribe(event, handler);
|
|
68
|
+
}
|
|
69
|
+
|
|
58
70
|
return this.delegate;
|
|
59
71
|
}
|
|
60
72
|
|
|
@@ -134,6 +146,42 @@ export class ReconnectingWebSocketTransport extends Transport {
|
|
|
134
146
|
this.connectionPromise = undefined;
|
|
135
147
|
}
|
|
136
148
|
|
|
149
|
+
override subscribe(...args: Parameters<Transport["subscribe"]>): void {
|
|
150
|
+
// Register handler on parent Transport
|
|
151
|
+
super.subscribe(...args);
|
|
152
|
+
|
|
153
|
+
const [event, handler] = args;
|
|
154
|
+
|
|
155
|
+
// Track the subscription
|
|
156
|
+
this.pendingSubscriptions.push({ event, handler });
|
|
157
|
+
|
|
158
|
+
// Also register on delegate if it exists
|
|
159
|
+
if (this.delegate) {
|
|
160
|
+
this.delegate.subscribe(event, handler);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
override unsubscribe(
|
|
165
|
+
...args: Parameters<Transport["unsubscribe"]>
|
|
166
|
+
): import("events").EventEmitter | undefined {
|
|
167
|
+
// Unregister from parent
|
|
168
|
+
const result = super.unsubscribe(...args);
|
|
169
|
+
|
|
170
|
+
const [event, handler] = args;
|
|
171
|
+
|
|
172
|
+
// Remove from pending subscriptions
|
|
173
|
+
this.pendingSubscriptions = this.pendingSubscriptions.filter(
|
|
174
|
+
(sub) => !(sub.event === event && sub.handler === handler),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Also unregister from delegate if it exists
|
|
178
|
+
if (this.delegate) {
|
|
179
|
+
this.delegate.unsubscribe(event, handler);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
137
185
|
override async sendData(
|
|
138
186
|
data: JSONRPCRequestData,
|
|
139
187
|
timeout: number | null | undefined,
|
package/src/css/app/Cell.css
CHANGED
|
@@ -249,7 +249,6 @@
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
/* Borderless styles for Cell */
|
|
252
|
-
|
|
253
252
|
&.borderless {
|
|
254
253
|
border-color: transparent;
|
|
255
254
|
|
|
@@ -258,7 +257,6 @@
|
|
|
258
257
|
}
|
|
259
258
|
|
|
260
259
|
/* Apply the original styles */
|
|
261
|
-
&:hover,
|
|
262
260
|
&:focus {
|
|
263
261
|
border: 1px solid var(--gray-4);
|
|
264
262
|
}
|
|
@@ -115,16 +115,18 @@ const TexComponent = ({
|
|
|
115
115
|
// isn't a simple way to do that in Python without bringing in a new
|
|
116
116
|
// dependency.
|
|
117
117
|
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
118
|
+
// When nested, the inner marimo-tex should not render because the outer
|
|
119
|
+
// marimo-tex's textContent includes the nested delimiters (||(||(x||)||))
|
|
120
|
+
// and will render correctly with displayMode: true. We detect this by
|
|
121
|
+
// checking if the parent element is also a marimo-tex.
|
|
122
|
+
const isNested = host.parentElement?.tagName.toLowerCase() === "marimo-tex";
|
|
121
123
|
|
|
122
124
|
// Re-render when the text content changes.
|
|
123
125
|
useLayoutEffect(() => {
|
|
124
|
-
if (ref.current) {
|
|
126
|
+
if (ref.current && !isNested) {
|
|
125
127
|
renderLatex(ref.current, currentTex);
|
|
126
128
|
}
|
|
127
|
-
}, [currentTex]);
|
|
129
|
+
}, [currentTex, isNested]);
|
|
128
130
|
|
|
129
131
|
return <span ref={ref} />;
|
|
130
132
|
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./worker-BR7KVExK.js";var t=e(((e,t)=>{t.exports={}}));export default t();
|