@marimo-team/islands 0.23.12-dev0 → 0.23.12-dev10
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-WqG-xX4l.js → ConnectedDataExplorerComponent-Du3_nUzI.js} +13 -13
- package/dist/{ErrorBoundary-BNx_OSVo.js → ErrorBoundary-DE6tzZf-.js} +2 -2
- package/dist/{any-language-editor-rPSlOll9.js → any-language-editor-DN1R-1KZ.js} +5 -5
- package/dist/{button-vQhauTmO.js → button-BacYv-bE.js} +7 -1
- package/dist/{capabilities-BEHzIS99.js → capabilities-D_4LYhSU.js} +1 -1
- package/dist/{chat-ui-k2kqhCv5.js → chat-ui-BEOvjkmJ.js} +16 -16
- package/dist/{check-nrzHDi45.js → check-C9OoNtR4.js} +1 -1
- package/dist/{code-visibility-BGJLYDS-.js → code-visibility-D1W_9sHW.js} +664 -663
- package/dist/{copy-UhDed7D4.js → copy-COam1EG7.js} +2 -2
- package/dist/{dist-DYGLrbYQ.js → dist--2Bqjvs0.js} +2 -2
- package/dist/{error-banner-BHAkVFc2.js → error-banner-DFPfz_Qf.js} +2 -2
- package/dist/{esm-Bqu9AE2K.js → esm-M837UxV5.js} +1 -1
- package/dist/{extends-9Yl5BEcg.js → extends-9MVIxxRo.js} +4 -4
- package/dist/{formats-BV4bOfMI.js → formats-d6MhLuQ9.js} +4 -4
- package/dist/{glide-data-editor-BDTq6YUb.js → glide-data-editor-DkzAInWG.js} +9 -9
- package/dist/{html-to-image-C86pQALH.js → html-to-image-Di0mtt6O.js} +73 -73
- package/dist/{input-AKkGXdyV.js → input-CbEz_aj_.js} +6 -6
- package/dist/{label-E3ZJXHu8.js → label-WfTSU8L4.js} +2 -2
- package/dist/{loader-YPuQvn1Y.js → loader-Boph2xIS.js} +1 -1
- package/dist/main.js +1619 -1592
- package/dist/{mermaid-QFAR9YgY.js → mermaid-CJW9vIyO.js} +5 -5
- package/dist/{process-output-nNw4OpSj.js → process-output-BLd4KuwX.js} +3 -3
- package/dist/{reveal-component-B2YCxhmm.js → reveal-component-BXP8Y-bq.js} +11 -11
- package/dist/{spec-B45_YCNI.js → spec-Bv-XlYiv.js} +4 -4
- package/dist/{strings-Cq2s9_EQ.js → strings-Dq_j3Rxw.js} +4 -4
- package/dist/style.css +2 -2
- package/dist/{swiper-component-BNa_4kh2.js → swiper-component-5HoSsPi1.js} +2 -2
- package/dist/{toDate-Do1xRzAo.js → toDate-D-l5s8nn.js} +3 -3
- package/dist/{tooltip-Bz3OAwrU.js → tooltip-Czds6Qr8.js} +3 -3
- package/dist/{types-D8gEGs4R.js → types-C2Ir191_.js} +1 -1
- package/dist/{useAsyncData-CL3o2p4i.js → useAsyncData-1Dhzjfwf.js} +1 -1
- package/dist/{useDateFormatter-BC6iSz9g.js → useDateFormatter-CMnRuVmN.js} +2 -2
- package/dist/{useDeepCompareMemoize-BPx2MuOK.js → useDeepCompareMemoize-CDWT3BDz.js} +1 -1
- package/dist/{useIframeCapabilities-C6Ta3EyP.js → useIframeCapabilities-DWIYvDh7.js} +1 -1
- package/dist/{useLifecycle-C3Ec71q0.js → useLifecycle-AHlswLw-.js} +3 -3
- package/dist/{useTheme-ZhT6uIu3.js → useTheme-BrYvK-_A.js} +2 -2
- package/dist/{vega-component-C3AWYGAL.js → vega-component-Pk6lyc_a.js} +10 -10
- package/dist/{zod-DXqkaI_w.js → zod-CijjQh4u.js} +1 -1
- package/package.json +3 -3
- package/src/components/ai/display-helpers.tsx +5 -5
- package/src/components/app-config/ai-config.tsx +5 -5
- package/src/components/app-config/mcp-config.tsx +3 -3
- package/src/components/chat/acp/agent-panel.tsx +3 -3
- package/src/components/chat/acp/blocks.tsx +36 -38
- package/src/components/chat/acp/common.tsx +12 -16
- package/src/components/chat/acp/scroll-to-bottom-button.tsx +1 -1
- package/src/components/chat/acp/session-tabs.tsx +2 -2
- package/src/components/chat/chat-history-popover.tsx +1 -1
- package/src/components/chat/chat-panel.tsx +47 -23
- package/src/components/data-table/TableBottomBar.tsx +4 -1
- package/src/components/data-table/columns.tsx +2 -2
- package/src/components/data-table/data-table.tsx +26 -17
- package/src/components/data-table/filter-pill-editor.tsx +1 -1
- package/src/components/dependency-graph/minimap-content.tsx +1 -1
- package/src/components/editor/RecoveryButton.tsx +1 -1
- package/src/components/editor/actions/pair-with-agent-modal.tsx +2 -2
- package/src/components/editor/actions/useNotebookActions.tsx +4 -4
- package/src/components/editor/ai/__tests__/completion-utils.test.ts +91 -1
- package/src/components/editor/ai/ai-completion-editor.tsx +1 -1
- package/src/components/editor/ai/completion-utils.ts +86 -1
- package/src/components/editor/cell/CreateCellButton.tsx +1 -1
- package/src/components/editor/chrome/panels/empty-state.tsx +1 -1
- package/src/components/editor/chrome/panels/outline/floating-outline.tsx +1 -1
- package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +1 -1
- package/src/components/editor/columns/cell-column.tsx +1 -1
- package/src/components/editor/columns/sortable-column.tsx +2 -2
- package/src/components/editor/output/MarimoErrorOutput.tsx +1 -1
- package/src/components/editor/output/TextOutput.tsx +2 -2
- package/src/components/home/components.tsx +4 -4
- package/src/components/icons/github.tsx +21 -0
- package/src/components/icons/youtube.tsx +21 -0
- package/src/components/slides/minimap.tsx +2 -2
- package/src/components/slides/reveal-component.tsx +1 -1
- package/src/components/storage/components.tsx +3 -7
- package/src/components/ui/alert.tsx +1 -1
- package/src/components/ui/command.tsx +2 -2
- package/src/components/ui/reorderable-list.tsx +1 -1
- package/src/components/ui/table.tsx +2 -5
- package/src/core/codemirror/language/languages/sql/renderers.tsx +60 -68
- package/src/core/hotkeys/hotkeys.ts +1 -0
- package/src/plugins/impl/DataTablePlugin.tsx +20 -1
- package/src/plugins/impl/FileBrowserPlugin.tsx +165 -74
- package/src/plugins/impl/MatrixPlugin.tsx +2 -2
- package/src/plugins/impl/TabsPlugin.tsx +1 -1
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +141 -1
- package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +314 -0
- package/src/plugins/impl/matplotlib/matplotlib-renderer.ts +1 -1
- package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +155 -98
- package/src/plugins/impl/mpl-interactive/__tests__/MplInteractivePlugin.test.tsx +154 -1
- package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +10 -0
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
/* oxlint-disable typescript/no-explicit-any */
|
|
3
|
+
/* oxlint-disable marimo/prefer-object-params -- the mocked mpl.js figure
|
|
4
|
+
constructor must match its real positional signature. */
|
|
5
|
+
import { render, waitFor } from "@testing-library/react";
|
|
2
6
|
import type { ExtractAtomValue } from "jotai";
|
|
3
7
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
8
|
import { hasRunAnyCellAtom } from "@/components/editor/cell/useRunCells";
|
|
@@ -7,9 +11,12 @@ import { parseUserConfig } from "@/core/config/config-schema";
|
|
|
7
11
|
import { initialModeAtom } from "@/core/mode";
|
|
8
12
|
import { store } from "@/core/state/jotai";
|
|
9
13
|
import { Logger } from "@/utils/Logger";
|
|
14
|
+
import { MODEL_MANAGER, Model } from "@/plugins/impl/anywidget/model";
|
|
15
|
+
import type { WidgetModelId } from "@/plugins/impl/anywidget/types";
|
|
10
16
|
import { visibleForTesting } from "../MplInteractivePlugin";
|
|
11
17
|
|
|
12
|
-
const { ensureMplJs, injectCss, resetMplJsLoading } =
|
|
18
|
+
const { ensureMplJs, injectCss, MplInteractiveSlot, resetMplJsLoading } =
|
|
19
|
+
visibleForTesting;
|
|
13
20
|
|
|
14
21
|
/**
|
|
15
22
|
* Clear every "notebook trust" signal `isTrustedVirtualFileUrl` consults so
|
|
@@ -151,3 +158,149 @@ describe("MplInteractivePlugin URL validation", () => {
|
|
|
151
158
|
});
|
|
152
159
|
});
|
|
153
160
|
});
|
|
161
|
+
|
|
162
|
+
const asModelId = (id: string): WidgetModelId => id as WidgetModelId;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Minimal stand-in for the global `window.mpl.figure` constructor. mpl.js
|
|
166
|
+
* builds the canvas DOM and wires `onopen`/`onmessage` onto the socket at
|
|
167
|
+
* construction; the mock reproduces just enough of that for the slot to mount
|
|
168
|
+
* and rebind.
|
|
169
|
+
*/
|
|
170
|
+
function installMplFigureMock(): ReturnType<typeof vi.fn> {
|
|
171
|
+
const ctor = vi.fn(function (
|
|
172
|
+
this: any,
|
|
173
|
+
id: string,
|
|
174
|
+
ws: any,
|
|
175
|
+
_ondownload: unknown,
|
|
176
|
+
container: HTMLElement,
|
|
177
|
+
) {
|
|
178
|
+
this.id = id;
|
|
179
|
+
this.ws = ws;
|
|
180
|
+
const root = document.createElement("div");
|
|
181
|
+
const canvasDiv = document.createElement("div");
|
|
182
|
+
canvasDiv.setAttribute("tabindex", "0");
|
|
183
|
+
root.append(canvasDiv);
|
|
184
|
+
container.append(root);
|
|
185
|
+
this.root = root;
|
|
186
|
+
this.send_message = vi.fn();
|
|
187
|
+
ws.onopen = vi.fn();
|
|
188
|
+
ws.onmessage = vi.fn();
|
|
189
|
+
});
|
|
190
|
+
(window as unknown as { mpl: unknown }).mpl = {
|
|
191
|
+
figure: ctor,
|
|
192
|
+
toolbar_items: [],
|
|
193
|
+
};
|
|
194
|
+
return ctor;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function makeModel(): Model<Record<string, never>> {
|
|
198
|
+
return new Model(
|
|
199
|
+
{},
|
|
200
|
+
{
|
|
201
|
+
sendUpdate: vi.fn().mockResolvedValue(undefined),
|
|
202
|
+
sendCustomMessage: vi.fn().mockResolvedValue(undefined),
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function makeProps(modelId: WidgetModelId) {
|
|
208
|
+
return {
|
|
209
|
+
data: {
|
|
210
|
+
mplJsUrl: "./@file/1-mpl.js",
|
|
211
|
+
cssUrl: "./@file/2-mpl.css",
|
|
212
|
+
toolbarImages: {},
|
|
213
|
+
width: 640,
|
|
214
|
+
height: 480,
|
|
215
|
+
},
|
|
216
|
+
value: { model_id: modelId },
|
|
217
|
+
host: document.createElement("div"),
|
|
218
|
+
setValue: vi.fn(),
|
|
219
|
+
functions: {},
|
|
220
|
+
} as any;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
describe("MplInteractiveSlot rerun rebinding", () => {
|
|
224
|
+
beforeEach(() => {
|
|
225
|
+
vi.spyOn(Logger, "error").mockImplementation(() => {});
|
|
226
|
+
resetMplJsLoading();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
afterEach(() => {
|
|
230
|
+
vi.restoreAllMocks();
|
|
231
|
+
delete (window as { mpl?: unknown }).mpl;
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("rebinds to a new model without rebuilding the figure DOM", async () => {
|
|
235
|
+
const ctor = installMplFigureMock();
|
|
236
|
+
const idA = asModelId("model-a");
|
|
237
|
+
const idB = asModelId("model-b");
|
|
238
|
+
MODEL_MANAGER.set(idA, makeModel());
|
|
239
|
+
MODEL_MANAGER.set(idB, makeModel());
|
|
240
|
+
|
|
241
|
+
const { container, rerender } = render(
|
|
242
|
+
<MplInteractiveSlot {...makeProps(idA)} />,
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
await waitFor(() => expect(ctor).toHaveBeenCalledTimes(1));
|
|
246
|
+
|
|
247
|
+
const figureRoot = ctor.mock.instances[0].root as HTMLElement;
|
|
248
|
+
const socket = ctor.mock.calls[0][1];
|
|
249
|
+
const slot = container.querySelector(".mpl-interactive-figure");
|
|
250
|
+
expect(slot?.contains(figureRoot)).toBe(true);
|
|
251
|
+
// One handshake on the initial bind.
|
|
252
|
+
expect(socket.onopen).toHaveBeenCalledTimes(1);
|
|
253
|
+
const setSendHandler = vi.spyOn(socket, "setSendHandler");
|
|
254
|
+
|
|
255
|
+
// Cell re-run: only the model id changes.
|
|
256
|
+
rerender(<MplInteractiveSlot {...makeProps(idB)} />);
|
|
257
|
+
|
|
258
|
+
// The new model is bound through the existing socket, with a fresh
|
|
259
|
+
// handshake, and the figure is never reconstructed.
|
|
260
|
+
await waitFor(() => expect(socket.onopen).toHaveBeenCalledTimes(2));
|
|
261
|
+
expect(ctor).toHaveBeenCalledTimes(1);
|
|
262
|
+
expect(setSendHandler).toHaveBeenCalledTimes(1);
|
|
263
|
+
// The same rendered DOM is still in place — not cleared and rebuilt.
|
|
264
|
+
expect(container.querySelector(".mpl-interactive-figure")).toBe(slot);
|
|
265
|
+
expect(slot?.contains(figureRoot)).toBe(true);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("detaches the previous model's listener on each rerun (no buildup)", async () => {
|
|
269
|
+
installMplFigureMock();
|
|
270
|
+
// Unique ids: MODEL_MANAGER is a module singleton whose deferreds resolve
|
|
271
|
+
// once, so reusing ids from another test would return that test's models.
|
|
272
|
+
const idA = asModelId("leak-a");
|
|
273
|
+
const idB = asModelId("leak-b");
|
|
274
|
+
const idC = asModelId("leak-c");
|
|
275
|
+
const modelA = makeModel();
|
|
276
|
+
const modelB = makeModel();
|
|
277
|
+
const modelC = makeModel();
|
|
278
|
+
MODEL_MANAGER.set(idA, modelA);
|
|
279
|
+
MODEL_MANAGER.set(idB, modelB);
|
|
280
|
+
MODEL_MANAGER.set(idC, modelC);
|
|
281
|
+
|
|
282
|
+
const onA = vi.spyOn(modelA, "on");
|
|
283
|
+
const offA = vi.spyOn(modelA, "off");
|
|
284
|
+
const onB = vi.spyOn(modelB, "on");
|
|
285
|
+
const offB = vi.spyOn(modelB, "off");
|
|
286
|
+
const onC = vi.spyOn(modelC, "on");
|
|
287
|
+
const offC = vi.spyOn(modelC, "off");
|
|
288
|
+
|
|
289
|
+
const { rerender } = render(<MplInteractiveSlot {...makeProps(idA)} />);
|
|
290
|
+
await waitFor(() =>
|
|
291
|
+
expect(onA).toHaveBeenCalledWith("msg:custom", expect.any(Function)),
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
rerender(<MplInteractiveSlot {...makeProps(idB)} />);
|
|
295
|
+
await waitFor(() => expect(onB).toHaveBeenCalled());
|
|
296
|
+
|
|
297
|
+
rerender(<MplInteractiveSlot {...makeProps(idC)} />);
|
|
298
|
+
await waitFor(() => expect(onC).toHaveBeenCalled());
|
|
299
|
+
|
|
300
|
+
// Each superseded model had its listener detached exactly once when the
|
|
301
|
+
// next bind replaced it; the current model's listener stays attached.
|
|
302
|
+
expect(offA).toHaveBeenCalledTimes(1);
|
|
303
|
+
expect(offB).toHaveBeenCalledTimes(1);
|
|
304
|
+
expect(offC).not.toHaveBeenCalled();
|
|
305
|
+
});
|
|
306
|
+
});
|
|
@@ -21,6 +21,16 @@ export class MplCommWebSocket {
|
|
|
21
21
|
this.sendFn = sendFn;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Re-point outbound sends at a new backend comm without recreating the
|
|
26
|
+
* socket. The figure manager is reused across cell reruns, so the same
|
|
27
|
+
* socket instance stays bound to mpl.js (which wires `onopen`/`onmessage`
|
|
28
|
+
* onto it at construction); only the comm behind it changes.
|
|
29
|
+
*/
|
|
30
|
+
setSendHandler(sendFn: (msg: unknown) => void): void {
|
|
31
|
+
this.sendFn = sendFn;
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
/**
|
|
25
35
|
* Called by mpl.js to send a message to the backend.
|
|
26
36
|
* mpl.js always sends JSON strings.
|