@marimo-team/frontend 0.23.1-dev9 → 0.23.1

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.
Files changed (68) hide show
  1. package/dist/assets/{JsonOutput-BY31ccA7.js → JsonOutput-CavtrueA.js} +1 -1
  2. package/dist/assets/{MarimoErrorOutput--Yd2Aw0J.js → MarimoErrorOutput-Bmp8DLLo.js} +1 -1
  3. package/dist/assets/RenderHTML-CM3WMmA8.js +1 -0
  4. package/dist/assets/{add-connection-dialog-CjvNOKgb.js → add-connection-dialog-BGZvJkor.js} +1 -1
  5. package/dist/assets/{agent-panel-C24uwabG.js → agent-panel-BvL9Lu9c.js} +1 -1
  6. package/dist/assets/{cell-editor-zW0u82sK.js → cell-editor-B40o_zx_.js} +1 -1
  7. package/dist/assets/{chat-display-DsHMZa9F.js → chat-display-M_nvYuHH.js} +1 -1
  8. package/dist/assets/{chat-panel-o9D3upnX.js → chat-panel-BMOW93uQ.js} +1 -1
  9. package/dist/assets/{chat-ui-BYS03y86.js → chat-ui-DyeimpVh.js} +1 -1
  10. package/dist/assets/{column-preview-Dwv5a_zE.js → column-preview-AfcgbFG_.js} +1 -1
  11. package/dist/assets/{command-palette-BYbKGSF3.js → command-palette-BgvdyU3B.js} +1 -1
  12. package/dist/assets/{documentation-panel-CA2pWMgB.js → documentation-panel-DUPcsi8P.js} +1 -1
  13. package/dist/assets/{edit-page-CMUN3ESy.js → edit-page-DD4uEDmX.js} +4 -4
  14. package/dist/assets/{error-panel-CbqfK1HJ.js → error-panel-DQOeSv5-.js} +1 -1
  15. package/dist/assets/{file-explorer-panel-CbS8z-JR.js → file-explorer-panel-B67zjs2X.js} +1 -1
  16. package/dist/assets/{form-DLyXacSF.js → form-BJ6VFU8l.js} +1 -1
  17. package/dist/assets/{hooks-kZJc1iBf.js → hooks-DvwShzDb.js} +1 -1
  18. package/dist/assets/index-y6osgSWB.js +42 -0
  19. package/dist/assets/{layout-tmN-U1zs.js → layout-erv8pLIP.js} +1 -1
  20. package/dist/assets/{panels-CLfdzLPR.js → panels-1u-RE72f.js} +1 -1
  21. package/dist/assets/{run-page-DPuH6QY4.js → run-page-DfWH_1mz.js} +1 -1
  22. package/dist/assets/{scratchpad-panel-BsMm0GQP.js → scratchpad-panel-CnaiXtoJ.js} +1 -1
  23. package/dist/assets/{session-panel-CTDzGShO.js → session-panel-C68GBFwH.js} +1 -1
  24. package/dist/assets/{snippets-panel-CWof0wHk.js → snippets-panel-BmIdR0lc.js} +1 -1
  25. package/dist/assets/state-D1n-olwf.js +3 -0
  26. package/dist/assets/{useNotebookActions-DHBEqrc_.js → useNotebookActions-Ch1o32Jw.js} +1 -1
  27. package/dist/index.html +7 -7
  28. package/package.json +4 -4
  29. package/src/core/islands/__tests__/bridge.test.ts +2 -12
  30. package/src/core/islands/__tests__/islands-harness.test.ts +348 -0
  31. package/src/core/islands/__tests__/parse.test.ts +466 -24
  32. package/src/core/islands/__tests__/test-utils.tsx +263 -0
  33. package/src/core/islands/bootstrap.ts +265 -0
  34. package/src/core/islands/bridge.ts +154 -75
  35. package/src/core/islands/components/IslandControls.tsx +103 -0
  36. package/src/core/islands/components/__tests__/IslandControls.test.tsx +185 -0
  37. package/src/core/islands/components/__tests__/useIslandControls.test.ts +208 -0
  38. package/src/core/islands/components/output-wrapper.tsx +76 -93
  39. package/src/core/islands/components/useIslandControls.ts +60 -0
  40. package/src/core/islands/components/web-components.tsx +168 -40
  41. package/src/core/islands/constants.ts +28 -0
  42. package/src/core/islands/main.ts +7 -205
  43. package/src/core/islands/parse.ts +73 -26
  44. package/src/core/islands/worker-factory.ts +86 -0
  45. package/src/plugins/core/RenderHTML.tsx +9 -0
  46. package/src/plugins/core/__test__/RenderHTML.test.ts +27 -0
  47. package/src/plugins/core/__test__/trusted-url.test.ts +48 -0
  48. package/src/plugins/core/registerReactComponent.tsx +11 -8
  49. package/src/plugins/core/trusted-url.ts +20 -0
  50. package/src/plugins/impl/ButtonPlugin.tsx +4 -6
  51. package/src/plugins/impl/CodeEditorPlugin.tsx +15 -18
  52. package/src/plugins/impl/DataEditorPlugin.tsx +8 -14
  53. package/src/plugins/impl/DataTablePlugin.tsx +8 -9
  54. package/src/plugins/impl/FileUploadPlugin.tsx +39 -43
  55. package/src/plugins/impl/FormPlugin.tsx +2 -6
  56. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +27 -1
  57. package/src/plugins/impl/anywidget/widget-binding.ts +13 -0
  58. package/src/plugins/impl/chat/ChatPlugin.tsx +17 -20
  59. package/src/plugins/impl/data-explorer/DataExplorerPlugin.tsx +5 -8
  60. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +21 -0
  61. package/src/plugins/impl/mpl-interactive/__tests__/MplInteractivePlugin.test.tsx +119 -0
  62. package/src/plugins/impl/panel/PanelPlugin.tsx +31 -10
  63. package/src/plugins/impl/panel/__tests__/PanelPlugin.test.ts +60 -0
  64. package/src/plugins/impl/vega/VegaPlugin.tsx +5 -8
  65. package/src/plugins/layout/NavigationMenuPlugin.tsx +2 -6
  66. package/dist/assets/RenderHTML-CbuarQqA.js +0 -1
  67. package/dist/assets/index-Bm25ctN7.js +0 -42
  68. package/dist/assets/state-BvnlMKdT.js +0 -3
@@ -3,7 +3,6 @@ import "../vega/vega.css";
3
3
 
4
4
  import React from "react";
5
5
  import { z } from "zod";
6
- import { TooltipProvider } from "@/components/ui/tooltip";
7
6
  import { createPlugin } from "@/plugins/core/builder";
8
7
  import type { DataExplorerState } from "./ConnectedDataExplorerComponent";
9
8
 
@@ -21,11 +20,9 @@ export const DataExplorerPlugin = createPlugin<DataExplorerState>(
21
20
  }),
22
21
  )
23
22
  .renderer((props) => (
24
- <TooltipProvider>
25
- <LazyDataExplorerComponent
26
- {...props.data}
27
- value={props.value}
28
- setValue={props.setValue}
29
- />
30
- </TooltipProvider>
23
+ <LazyDataExplorerComponent
24
+ {...props.data}
25
+ value={props.value}
26
+ setValue={props.setValue}
27
+ />
31
28
  ));
@@ -5,12 +5,14 @@ import { useCallback, useEffect, useRef } from "react";
5
5
  import { z } from "zod";
6
6
  import { useEventListener } from "@/hooks/useEventListener";
7
7
  import { createPlugin } from "@/plugins/core/builder";
8
+ import { isTrustedVirtualFileUrl } from "@/plugins/core/trusted-url";
8
9
  import { MODEL_MANAGER, type Model } from "@/plugins/impl/anywidget/model";
9
10
  import type { ModelState, WidgetModelId } from "@/plugins/impl/anywidget/types";
10
11
  import type { IPluginProps } from "@/plugins/types";
11
12
  import { downloadBlob } from "@/utils/download";
12
13
  import { Logger } from "@/utils/Logger";
13
14
  import { MplCommWebSocket } from "./mpl-websocket-shim";
15
+ import { Functions } from "@/utils/functions";
14
16
 
15
17
  const MPL_SCOPE_CLASS = "mpl-interactive-figure";
16
18
 
@@ -73,6 +75,11 @@ async function ensureMplJs(jsUrl: string): Promise<void> {
73
75
  if (window.mpl) {
74
76
  return;
75
77
  }
78
+ if (!isTrustedVirtualFileUrl(jsUrl)) {
79
+ throw new Error(
80
+ `Refusing to load mpl.js from untrusted URL: ${String(jsUrl)}`,
81
+ );
82
+ }
76
83
  if (mplJsLoading) {
77
84
  return mplJsLoading;
78
85
  }
@@ -148,6 +155,12 @@ function patchToolbarImages(
148
155
  }
149
156
 
150
157
  function injectCss(container: HTMLElement, cssUrl: string): () => void {
158
+ if (!isTrustedVirtualFileUrl(cssUrl)) {
159
+ Logger.error(
160
+ `Refusing to load mpl CSS from untrusted URL: ${String(cssUrl)}`,
161
+ );
162
+ return Functions.NOOP;
163
+ }
151
164
  const link = document.createElement("link");
152
165
  link.rel = "stylesheet";
153
166
  link.href = cssUrl;
@@ -307,3 +320,11 @@ const MplInteractiveSlot = (props: IPluginProps<ModelIdRef, Data>) => {
307
320
  // Must match _MPL_SCOPE in from_mpl_interactive.py
308
321
  return <div ref={containerRef} className={MPL_SCOPE_CLASS} />;
309
322
  };
323
+
324
+ export const visibleForTesting = {
325
+ ensureMplJs,
326
+ injectCss,
327
+ resetMplJsLoading: () => {
328
+ mplJsLoading = null;
329
+ },
330
+ };
@@ -0,0 +1,119 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { Logger } from "@/utils/Logger";
4
+ import { visibleForTesting } from "../MplInteractivePlugin";
5
+
6
+ const { ensureMplJs, injectCss, resetMplJsLoading } = visibleForTesting;
7
+
8
+ describe("MplInteractivePlugin URL validation", () => {
9
+ beforeEach(() => {
10
+ // Reset module-level script-loading state and any stubs.
11
+ delete (window as { mpl?: unknown }).mpl;
12
+ resetMplJsLoading();
13
+ // Remove any scripts the tests added to document.head.
14
+ for (const el of document.head.querySelectorAll(
15
+ "script[data-test-mpl],link[data-test-mpl]",
16
+ )) {
17
+ el.remove();
18
+ }
19
+ });
20
+
21
+ afterEach(() => {
22
+ vi.restoreAllMocks();
23
+ });
24
+
25
+ describe("ensureMplJs", () => {
26
+ it("rejects the PoC attack URL without creating a <script>", async () => {
27
+ const appendSpy = vi.spyOn(document.head, "append");
28
+ await expect(ensureMplJs("http://127.0.0.1:8820/poc.js")).rejects.toThrow(
29
+ /untrusted/i,
30
+ );
31
+ expect(appendSpy).not.toHaveBeenCalled();
32
+ });
33
+
34
+ it.each([
35
+ "https://evil.example.com/x.js",
36
+ "//evil.example.com/x.js",
37
+ "javascript:alert(1)",
38
+ "data:text/javascript;base64,YWxlcnQoMSk=",
39
+ "./@file/x.js?redirect=http://evil.com",
40
+ ])("rejects %s", async (url) => {
41
+ const appendSpy = vi.spyOn(document.head, "append");
42
+ await expect(ensureMplJs(url)).rejects.toThrow(/untrusted/i);
43
+ expect(appendSpy).not.toHaveBeenCalled();
44
+ });
45
+
46
+ it("is a no-op when window.mpl is already present", async () => {
47
+ (window as { mpl?: unknown }).mpl = {};
48
+ const appendSpy = vi.spyOn(document.head, "append");
49
+ // Even a malicious URL should be ignored — short-circuit happens first.
50
+ await expect(
51
+ ensureMplJs("http://evil.example.com/x.js"),
52
+ ).resolves.toBeUndefined();
53
+ expect(appendSpy).not.toHaveBeenCalled();
54
+ });
55
+
56
+ it("creates a <script src> for a trusted virtual file URL", async () => {
57
+ const appendSpy = vi
58
+ .spyOn(document.head, "append")
59
+ .mockImplementation((...nodes) => {
60
+ // Simulate a successful load so ensureMplJs resolves.
61
+ for (const node of nodes) {
62
+ if (node instanceof HTMLScriptElement) {
63
+ queueMicrotask(() => node.onload?.(new Event("load")));
64
+ }
65
+ }
66
+ });
67
+
68
+ await expect(ensureMplJs("./@file/123-mpl.js")).resolves.toBeUndefined();
69
+
70
+ expect(appendSpy).toHaveBeenCalledTimes(1);
71
+ const appended = appendSpy.mock.calls[0][0] as HTMLScriptElement;
72
+ expect(appended.tagName).toBe("SCRIPT");
73
+ expect(appended.src).toContain("@file/123-mpl.js");
74
+ });
75
+ });
76
+
77
+ describe("injectCss", () => {
78
+ it("refuses to append <link> for the PoC attack CSS URL", () => {
79
+ const container = document.createElement("div");
80
+ const loggerSpy = vi.spyOn(Logger, "error").mockImplementation(() => {});
81
+
82
+ const cleanup = injectCss(container, "http://127.0.0.1:8820/x.css");
83
+
84
+ expect(container.querySelector("link")).toBeNull();
85
+ expect(loggerSpy).toHaveBeenCalledWith(
86
+ expect.stringContaining("untrusted"),
87
+ );
88
+ // Cleanup must be safe to call even when nothing was appended.
89
+ expect(() => cleanup()).not.toThrow();
90
+ });
91
+
92
+ it.each([
93
+ "https://evil.example.com/x.css",
94
+ "javascript:alert(1)",
95
+ "data:text/css,body{background:red}",
96
+ ])("refuses to append <link> for %s", (url) => {
97
+ const container = document.createElement("div");
98
+ vi.spyOn(Logger, "error").mockImplementation(() => {});
99
+
100
+ injectCss(container, url);
101
+
102
+ expect(container.querySelector("link")).toBeNull();
103
+ });
104
+
105
+ it("appends a <link> for a trusted virtual file URL", () => {
106
+ const container = document.createElement("div");
107
+
108
+ const cleanup = injectCss(container, "./@file/456-mpl.css");
109
+
110
+ const link = container.querySelector("link");
111
+ expect(link).not.toBeNull();
112
+ expect(link?.rel).toBe("stylesheet");
113
+ expect(link?.getAttribute("href")).toBe("./@file/456-mpl.css");
114
+
115
+ cleanup();
116
+ expect(container.querySelector("link")).toBeNull();
117
+ });
118
+ });
119
+ });
@@ -10,6 +10,7 @@ import {
10
10
  } from "@/hooks/useEventListener";
11
11
  import { createPlugin } from "@/plugins/core/builder";
12
12
  import { rpc } from "@/plugins/core/rpc";
13
+ import { isTrustedVirtualFileUrl } from "@/plugins/core/trusted-url";
13
14
  import type { IPluginProps } from "@/plugins/types";
14
15
  import { Logger } from "@/utils/Logger";
15
16
  import { EventBuffer, extractBuffers, MessageSchema } from "./utils";
@@ -64,7 +65,7 @@ declare global {
64
65
  }
65
66
 
66
67
  interface PanelData {
67
- extension: string | null;
68
+ extensionUrl: string | null;
68
69
  docs_json: Record<string, unknown>;
69
70
  render_json: {
70
71
  roots: Record<string, string>;
@@ -85,7 +86,7 @@ type PluginFunctions = {
85
86
  export const PanelPlugin = createPlugin<T>("marimo-panel")
86
87
  .withData(
87
88
  z.object({
88
- extension: z.string().nullable(),
89
+ extensionUrl: z.string().nullable(),
89
90
  docs_json: z.record(z.string(), z.unknown()),
90
91
  render_json: z
91
92
  .object({
@@ -110,9 +111,34 @@ function isBokehLoaded() {
110
111
  return window.Bokeh != null;
111
112
  }
112
113
 
114
+ /**
115
+ * Append a `<script src>` for the bokeh/panel extension.
116
+ *
117
+ * The URL must be a marimo virtual file path; anything else (e.g. an
118
+ * attacker-controlled URL injected via a raw `<marimo-panel>` element in a
119
+ * markdown cell) is refused.
120
+ */
121
+ export function loadPanelExtension(extensionUrl: string | null): boolean {
122
+ if (!extensionUrl) {
123
+ return false;
124
+ }
125
+ if (!isTrustedVirtualFileUrl(extensionUrl)) {
126
+ Logger.error(
127
+ `Refusing to load Panel extension from untrusted URL: ${String(
128
+ extensionUrl,
129
+ )}`,
130
+ );
131
+ return false;
132
+ }
133
+ const script = document.createElement("script");
134
+ script.src = extensionUrl;
135
+ document.head.append(script);
136
+ return true;
137
+ }
138
+
113
139
  const PanelSlot = (props: Props) => {
114
140
  const { data, functions, host } = props;
115
- const { extension, docs_json: docsJson, render_json: renderJson } = data;
141
+ const { extensionUrl, docs_json: docsJson, render_json: renderJson } = data;
116
142
  const ref = useRef<HTMLDivElement>(null);
117
143
  const rootModelIdRef = useRef<string | null>(null);
118
144
  const receiverRef = useRef<InstanceType<
@@ -173,12 +199,7 @@ const PanelSlot = (props: Props) => {
173
199
  return;
174
200
  }
175
201
 
176
- // Load the extension
177
- if (extension) {
178
- const script = document.createElement("script");
179
- script.innerHTML = extension;
180
- document.head.append(script);
181
- }
202
+ loadPanelExtension(extensionUrl);
182
203
 
183
204
  // Check if Bokeh is loaded every 10ms
184
205
  const checkBokeh = setInterval(() => {
@@ -189,7 +210,7 @@ const PanelSlot = (props: Props) => {
189
210
  }, 10);
190
211
 
191
212
  return () => clearInterval(checkBokeh);
192
- }, [extension, setLoaded]);
213
+ }, [extensionUrl, setLoaded]);
193
214
 
194
215
  // Listen for incoming messages
195
216
  useEventListener(
@@ -0,0 +1,60 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { Logger } from "@/utils/Logger";
4
+ import { loadPanelExtension } from "../PanelPlugin";
5
+
6
+ describe("loadPanelExtension", () => {
7
+ beforeEach(() => {
8
+ for (const el of document.head.querySelectorAll("script")) {
9
+ el.remove();
10
+ }
11
+ });
12
+
13
+ afterEach(() => {
14
+ vi.restoreAllMocks();
15
+ });
16
+
17
+ it("does nothing and returns false for null URL", () => {
18
+ const appendSpy = vi.spyOn(document.head, "append");
19
+ expect(loadPanelExtension(null)).toBe(false);
20
+ expect(appendSpy).not.toHaveBeenCalled();
21
+ });
22
+
23
+ it("refuses to load the PoC attack URL", () => {
24
+ const appendSpy = vi.spyOn(document.head, "append");
25
+ const loggerSpy = vi.spyOn(Logger, "error").mockImplementation(() => {});
26
+
27
+ expect(loadPanelExtension("http://127.0.0.1:8820/poc.js")).toBe(false);
28
+
29
+ expect(appendSpy).not.toHaveBeenCalled();
30
+ expect(loggerSpy).toHaveBeenCalledWith(
31
+ expect.stringContaining("untrusted"),
32
+ );
33
+ });
34
+
35
+ it.each([
36
+ "https://evil.example.com/x.js",
37
+ "//evil.example.com/x.js",
38
+ // An attacker embedding inline JS as a data URL — what the old plugin
39
+ // would have executed verbatim via script.innerHTML.
40
+ "data:text/javascript;base64,YWxlcnQoMSk=",
41
+ "javascript:alert(1)",
42
+ "./@file/x.js#http://evil.com",
43
+ ])("refuses to load %s", (url) => {
44
+ const appendSpy = vi.spyOn(document.head, "append");
45
+ vi.spyOn(Logger, "error").mockImplementation(() => {});
46
+
47
+ expect(loadPanelExtension(url)).toBe(false);
48
+ expect(appendSpy).not.toHaveBeenCalled();
49
+ });
50
+
51
+ it("appends a <script src> for a trusted virtual file URL", () => {
52
+ expect(loadPanelExtension("./@file/42-bokeh.js")).toBe(true);
53
+
54
+ const script = document.head.querySelector("script");
55
+ expect(script).not.toBeNull();
56
+ expect(script?.src).toContain("@file/42-bokeh.js");
57
+ // Must NOT populate innerHTML — that was the original vulnerability sink.
58
+ expect(script?.innerHTML).toBe("");
59
+ });
60
+ });
@@ -8,7 +8,6 @@ import type { Data, VegaComponentState } from "./vega-component";
8
8
 
9
9
  import "./vega.css";
10
10
  import React, { type JSX } from "react";
11
- import { TooltipProvider } from "@/components/ui/tooltip";
12
11
 
13
12
  const LazyVegaComponent = React.lazy(() => import("./vega-component"));
14
13
 
@@ -29,13 +28,11 @@ export class VegaPlugin implements IPlugin<VegaComponentState, Data> {
29
28
 
30
29
  render(props: IPluginProps<VegaComponentState, Data>): JSX.Element {
31
30
  return (
32
- <TooltipProvider>
33
- <LazyVegaComponent
34
- value={props.value}
35
- setValue={props.setValue}
36
- {...props.data}
37
- />
38
- </TooltipProvider>
31
+ <LazyVegaComponent
32
+ value={props.value}
33
+ setValue={props.setValue}
34
+ {...props.data}
35
+ />
39
36
  );
40
37
  }
41
38
  }
@@ -11,7 +11,7 @@ import {
11
11
  NavigationMenuTrigger,
12
12
  navigationMenuTriggerStyle,
13
13
  } from "@/components/ui/navigation";
14
- import { Tooltip, TooltipProvider } from "@/components/ui/tooltip";
14
+ import { Tooltip } from "@/components/ui/tooltip";
15
15
  import { renderHTML } from "@/plugins/core/RenderHTML";
16
16
  import { cn } from "@/utils/cn";
17
17
  import { appendQueryParams } from "@/utils/urls";
@@ -67,11 +67,7 @@ export class NavigationMenuPlugin implements IStatelessPlugin<Data> {
67
67
  });
68
68
 
69
69
  render(props: IStatelessPluginProps<Data>): JSX.Element {
70
- return (
71
- <TooltipProvider>
72
- <NavMenuComponent {...props.data} />
73
- </TooltipProvider>
74
- );
70
+ return <NavMenuComponent {...props.data} />;
75
71
  }
76
72
  }
77
73
 
@@ -1 +0,0 @@
1
- import{s as v}from"./chunk-LvLJmgfZ.js";import{i as j,p as y,u as w}from"./useEvent-D91BmmQi.js";import{t as R}from"./react-Bj1aDYRI.js";import{bn as S,gn as _,ii as c,it as H,m as I,ri as M,vt as N}from"./cells-BqYYXi6G.js";import{t as E}from"./compiler-runtime-B3qBwwSJ.js";import{_ as T}from"./useEventListener-DGjKht0c.js";import{o as z}from"./utils-8btzWeZg.js";import{t as C}from"./jsx-runtime-Blw4afVn.js";import{t as F}from"./tooltip-DmqhBBs6.js";import{t as L}from"./copy-icon-Ci08KCdY.js";import{t as V}from"./usePress-BXMIcLWP.js";import{n as k}from"./useDebounce-LVL1r3-M.js";import{t as q}from"./useRunCells-DFYAOTWd.js";var W=E(),m=v(R(),1),o=v(C(),1);const A=r=>{let t=(0,W.c)(16),e,n,i;t[0]===r?(e=t[1],n=t[2],i=t[3]):({href:n,children:e,...i}=r,t[0]=r,t[1]=e,t[2]=n,t[3]=i);let a=(0,m.useRef)(null),s;t[4]===n?s=t[5]:(s=()=>{let u=new URL(globalThis.location.href);u.hash=n,globalThis.history.pushState({},"",u.toString()),globalThis.dispatchEvent(new HashChangeEvent("hashchange"));let x=n.slice(1),g=document.getElementById(x);g&&g.scrollIntoView({behavior:"smooth",block:"start"})},t[4]=n,t[5]=s);let l=s,f;t[6]===l?f=t[7]:(f={onPress:()=>{l()}},t[6]=l,t[7]=f);let{pressProps:h}=V(f),d;t[8]===l?d=t[9]:(d=u=>{u.preventDefault(),l()},t[8]=l,t[9]=d);let b=d,p;return t[10]!==e||t[11]!==b||t[12]!==n||t[13]!==h||t[14]!==i?(p=(0,o.jsx)("a",{ref:a,href:n,...h,onClick:b,...i,children:e}),t[10]=e,t[11]=b,t[12]=n,t[13]=h,t[14]=i,t[15]=p):p=t[15],p};async function D(r){let t=I().inOrderIds.at(0);if(t)try{let e=await _.request({document:r,cellId:t});if(!e||e.options.length===0)return;let n=r.split(".").pop()??r,i=e.options[0],a=e.options.find(s=>s.name===n)??i;a!=null&&a.completion_info&&j.set(S,{documentation:a.completion_info})}catch(e){T.debug(`Doc lookup failed for "${r}"`,e)}}var O=E();const P=r=>{let t=(0,O.c)(8),{qualifiedName:e,children:n}=r,i;t[0]===e?i=t[1]:(i=()=>{D(e)},t[0]=e,t[1]=i);let a=k(i,100),s;t[2]===a?s=t[3]:(s=()=>a.cancel(),t[2]=a,t[3]=s);let l;return t[4]!==n||t[5]!==a||t[6]!==s?(l=(0,o.jsx)("span",{onMouseEnter:a,onMouseLeave:s,children:n}),t[4]=n,t[5]=a,t[6]=s,t[7]=l):l=t[7],l};var $=y(r=>{let t=r(q),e=r(z);if(t||e)return!1;let n=!0;try{n=H()==="read"}catch{return!0}return!n});function B(){return w($)}var U=E(),Z=r=>{if(r instanceof c.Element&&!/^[A-Za-z][\w-]*$/.test(r.name))return m.createElement(m.Fragment)},G=(r,t)=>{if(t instanceof c.Element&&t.name==="body"){if((0,m.isValidElement)(r)&&"props"in r){let e=r.props.children;return(0,o.jsx)(o.Fragment,{children:e})}return}},J=(r,t)=>{if(t instanceof c.Element&&t.name==="html"){if((0,m.isValidElement)(r)&&"props"in r){let e=r.props.children;return(0,o.jsx)(o.Fragment,{children:e})}return}},K=r=>{if(r instanceof c.Element&&r.attribs&&r.name==="iframe"){let t=document.createElement("iframe");return Object.entries(r.attribs).forEach(([e,n])=>{e.startsWith('"')&&e.endsWith('"')&&(e=e.slice(1,-1)),t.setAttribute(e,n)}),(0,o.jsx)("div",{dangerouslySetInnerHTML:{__html:t.outerHTML}})}},Q=r=>{if(r instanceof c.Element&&r.name==="script"){let t=r.attribs.src;if(!t)return;if(!document.querySelector(`script[src="${t}"]`)){let e=document.createElement("script");e.src=t,document.head.append(e)}return(0,o.jsx)(o.Fragment,{})}},X=(r,t)=>{if(t instanceof c.Element&&t.name==="a"){let e=t.attribs.href;if(e!=null&&e.startsWith("#")&&!e.startsWith("#code/")){let n=null;return(0,m.isValidElement)(r)&&"props"in r&&(n=r.props.children),(0,o.jsx)(A,{href:e,...t.attribs,children:n})}}},Y=(r,t,e)=>{var n,i;if(t instanceof c.Element&&t.name==="div"&&((i=(n=t.attribs)==null?void 0:n.class)!=null&&i.includes("codehilite")))return(0,o.jsx)(rt,{children:r},e)},tt=(r,t)=>{var e;if(t instanceof c.Element&&((e=t.attribs)!=null&&e["data-marimo-doc"])){let n=t.attribs["data-marimo-doc"];return(0,o.jsx)(P,{qualifiedName:n,children:r})}},et=(r,t)=>{var e;if(t instanceof c.Element&&((e=t.attribs)!=null&&e["data-tooltip"])){let n=t.attribs["data-tooltip"];return(0,o.jsx)(F,{content:n,children:r})}},rt=r=>{let t=(0,U.c)(3),{children:e}=r,n=(0,m.useRef)(null),i;t[0]===Symbol.for("react.memo_cache_sentinel")?(i=(0,o.jsx)("div",{className:"absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity",children:(0,o.jsx)(L,{tooltip:!1,className:"p-1",value:()=>{var l;let s=(l=n.current)==null?void 0:l.firstChild;return s&&s.textContent||""}})}),t[0]=i):i=t[0];let a;return t[1]===e?a=t[2]:(a=(0,o.jsxs)("div",{className:"relative group codehilite-wrapper",ref:n,children:[e,i]}),t[1]=e,t[2]=a),a};const nt=({html:r,additionalReplacements:t=[],alwaysSanitizeHtml:e=!0})=>(0,o.jsx)(it,{html:r,alwaysSanitizeHtml:e,additionalReplacements:t});var it=({html:r,additionalReplacements:t=[],alwaysSanitizeHtml:e})=>{let n=B();return at({html:(0,m.useMemo)(()=>e||n?N(r):r,[r,e,n]),additionalReplacements:t})};function at({html:r,additionalReplacements:t=[]}){let e=[Z,K,Q,...t],n=[Y,X,tt,et,G,J];return M(r,{replace:(i,a)=>{for(let s of e){let l=s(i,a);if(l)return l}return i},transform:(i,a,s)=>{for(let l of n){let f=l(i,a,s);if(f)return f}return i}})}export{nt as t};