@marimo-team/islands 0.19.8-dev40 → 0.19.8-dev48
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/assets/__vite-browser-external-WSlCcXn_.js +1 -0
- package/dist/assets/worker-DUYMdbtA.js +73 -0
- package/dist/main.js +1740 -1691
- package/dist/style.css +1 -1
- package/dist/{useDeepCompareMemoize-CMGprt3H.js → useDeepCompareMemoize-BhZZsis0.js} +7 -3
- package/dist/{vega-component-DU3aSp4m.js → vega-component-DCxUyPnb.js} +1 -1
- package/package.json +1 -1
- package/src/components/app-config/optional-features.tsx +1 -1
- package/src/components/chat/__tests__/useFileState.test.tsx +93 -0
- package/src/components/chat/acp/agent-panel.tsx +26 -77
- package/src/components/chat/chat-components.tsx +114 -1
- package/src/components/chat/chat-panel.tsx +32 -104
- package/src/components/chat/chat-utils.ts +42 -0
- package/src/components/editor/ai/add-cell-with-ai.tsx +85 -53
- package/src/components/editor/ai/ai-completion-editor.tsx +15 -38
- package/src/components/editor/chrome/panels/packages-panel.tsx +12 -9
- package/src/core/islands/__tests__/bridge.test.ts +7 -2
- package/src/core/islands/bridge.ts +1 -1
- package/src/core/islands/main.ts +7 -0
- package/src/core/network/types.ts +2 -2
- package/src/core/wasm/bridge.ts +1 -1
- package/src/core/websocket/useMarimoKernelConnection.tsx +5 -15
- package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +86 -167
- package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +37 -123
- package/src/plugins/impl/anywidget/__tests__/model.test.ts +128 -122
- package/src/{utils/__tests__/data-views.test.ts → plugins/impl/anywidget/__tests__/serialization.test.ts} +42 -96
- package/src/plugins/impl/anywidget/model.ts +348 -223
- package/src/plugins/impl/anywidget/schemas.ts +32 -0
- package/src/{utils/data-views.ts → plugins/impl/anywidget/serialization.ts} +13 -36
- package/src/plugins/impl/anywidget/types.ts +27 -0
- package/src/plugins/impl/chat/chat-ui.tsx +22 -20
- package/src/utils/Deferred.ts +21 -0
- package/src/utils/json/base64.ts +38 -8
- package/dist/assets/__vite-browser-external-6-UwTyQC.js +0 -1
- package/dist/assets/worker-D3e5wDxM.js +0 -73
|
@@ -192,9 +192,11 @@ describe("IslandsPyodideBridge", () => {
|
|
|
192
192
|
const request = {
|
|
193
193
|
modelId: "widget-1",
|
|
194
194
|
message: {
|
|
195
|
+
method: "update" as const,
|
|
195
196
|
state: { value: 42 },
|
|
196
197
|
bufferPaths: [],
|
|
197
198
|
},
|
|
199
|
+
buffers: [],
|
|
198
200
|
};
|
|
199
201
|
|
|
200
202
|
await bridge.sendModelValue(request);
|
|
@@ -202,12 +204,14 @@ describe("IslandsPyodideBridge", () => {
|
|
|
202
204
|
expect(mockBridge).toHaveBeenCalledWith({
|
|
203
205
|
functionName: "put_control_request",
|
|
204
206
|
payload: {
|
|
205
|
-
type: "
|
|
207
|
+
type: "model",
|
|
206
208
|
modelId: "widget-1",
|
|
207
209
|
message: {
|
|
210
|
+
method: "update",
|
|
208
211
|
state: { value: 42 },
|
|
209
212
|
bufferPaths: [],
|
|
210
213
|
},
|
|
214
|
+
buffers: [],
|
|
211
215
|
},
|
|
212
216
|
});
|
|
213
217
|
});
|
|
@@ -226,7 +230,8 @@ describe("IslandsPyodideBridge", () => {
|
|
|
226
230
|
await bridge.sendRun({ cellIds: [], codes: [] });
|
|
227
231
|
await bridge.sendModelValue({
|
|
228
232
|
modelId: "",
|
|
229
|
-
message: { state: {}, bufferPaths: [] },
|
|
233
|
+
message: { method: "update", state: {}, bufferPaths: [] },
|
|
234
|
+
buffers: [],
|
|
230
235
|
});
|
|
231
236
|
|
|
232
237
|
// All calls should have the type field
|
|
@@ -140,7 +140,7 @@ export class IslandsPyodideBridge implements RunRequests, EditRequests {
|
|
|
140
140
|
|
|
141
141
|
sendModelValue: RunRequests["sendModelValue"] = async (request) => {
|
|
142
142
|
await this.putControlRequest({
|
|
143
|
-
type: "
|
|
143
|
+
type: "model",
|
|
144
144
|
...request,
|
|
145
145
|
});
|
|
146
146
|
return null;
|
package/src/core/islands/main.ts
CHANGED
|
@@ -13,6 +13,10 @@ import "iconify-icon";
|
|
|
13
13
|
|
|
14
14
|
import { toast } from "@/components/ui/use-toast";
|
|
15
15
|
import { renderHTML } from "@/plugins/core/RenderHTML";
|
|
16
|
+
import {
|
|
17
|
+
handleWidgetMessage,
|
|
18
|
+
MODEL_MANAGER,
|
|
19
|
+
} from "@/plugins/impl/anywidget/model";
|
|
16
20
|
import { initializePlugins } from "@/plugins/plugins";
|
|
17
21
|
import { logNever } from "@/utils/assertNever";
|
|
18
22
|
import { Functions } from "@/utils/functions";
|
|
@@ -194,6 +198,9 @@ export async function initialize() {
|
|
|
194
198
|
return;
|
|
195
199
|
case "kernel-startup-error":
|
|
196
200
|
return;
|
|
201
|
+
case "model-lifecycle":
|
|
202
|
+
handleWidgetMessage(MODEL_MANAGER, msg.data);
|
|
203
|
+
return;
|
|
197
204
|
default:
|
|
198
205
|
logNever(msg.data);
|
|
199
206
|
}
|
|
@@ -78,7 +78,7 @@ export interface SetCellConfigRequest {
|
|
|
78
78
|
configs: Record<CellId, Partial<CellConfig>>;
|
|
79
79
|
}
|
|
80
80
|
export type UpdateUIElementRequest = schemas["UpdateUIElementRequest"];
|
|
81
|
-
export type
|
|
81
|
+
export type ModelRequest = schemas["ModelRequest"];
|
|
82
82
|
export type UpdateCellIdsRequest = schemas["UpdateCellIdsRequest"];
|
|
83
83
|
export type UpdateUserConfigRequest = schemas["UpdateUserConfigRequest"];
|
|
84
84
|
export type ShutdownSessionRequest = schemas["ShutdownSessionRequest"];
|
|
@@ -110,7 +110,7 @@ export type LspServerHealth = schemas["LspServerHealth"];
|
|
|
110
110
|
*/
|
|
111
111
|
export interface RunRequests {
|
|
112
112
|
sendComponentValues: (request: UpdateUIElementValuesRequest) => Promise<null>;
|
|
113
|
-
sendModelValue: (request:
|
|
113
|
+
sendModelValue: (request: ModelRequest) => Promise<null>;
|
|
114
114
|
sendInstantiate: (request: InstantiateNotebookRequest) => Promise<null>;
|
|
115
115
|
sendFunctionRequest: (request: InvokeFunctionRequest) => Promise<null>;
|
|
116
116
|
}
|
package/src/core/wasm/bridge.ts
CHANGED
|
@@ -541,7 +541,7 @@ export class PyodideBridge implements RunRequests, EditRequests {
|
|
|
541
541
|
|
|
542
542
|
sendModelValue: RunRequests["sendModelValue"] = async (request) => {
|
|
543
543
|
await this.putControlRequest({
|
|
544
|
-
type: "
|
|
544
|
+
type: "model",
|
|
545
545
|
...request,
|
|
546
546
|
});
|
|
547
547
|
return null;
|
|
@@ -11,7 +11,6 @@ import { useConnectionTransport } from "@/core/websocket/useWebSocket";
|
|
|
11
11
|
import { renderHTML } from "@/plugins/core/RenderHTML";
|
|
12
12
|
import {
|
|
13
13
|
handleWidgetMessage,
|
|
14
|
-
isMessageWidgetState,
|
|
15
14
|
MODEL_MANAGER,
|
|
16
15
|
} from "@/plugins/impl/anywidget/model";
|
|
17
16
|
import { logNever } from "@/utils/assertNever";
|
|
@@ -141,31 +140,22 @@ export function useMarimoKernelConnection(opts: {
|
|
|
141
140
|
return;
|
|
142
141
|
|
|
143
142
|
case "send-ui-element-message": {
|
|
144
|
-
const modelId = msg.data.model_id;
|
|
145
143
|
const uiElement = msg.data.ui_element;
|
|
146
|
-
const message = msg.data.message;
|
|
147
|
-
const buffers = safeExtractSetUIElementMessageBuffers(msg.data);
|
|
148
|
-
|
|
149
|
-
if (modelId && isMessageWidgetState(message)) {
|
|
150
|
-
handleWidgetMessage({
|
|
151
|
-
modelId,
|
|
152
|
-
msg: message,
|
|
153
|
-
buffers,
|
|
154
|
-
modelManager: MODEL_MANAGER,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
144
|
if (uiElement) {
|
|
145
|
+
const buffers = safeExtractSetUIElementMessageBuffers(msg.data);
|
|
159
146
|
UI_ELEMENT_REGISTRY.broadcastMessage(
|
|
160
147
|
uiElement as UIElementId,
|
|
161
148
|
msg.data.message,
|
|
162
149
|
buffers,
|
|
163
150
|
);
|
|
164
151
|
}
|
|
165
|
-
|
|
166
152
|
return;
|
|
167
153
|
}
|
|
168
154
|
|
|
155
|
+
case "model-lifecycle":
|
|
156
|
+
handleWidgetMessage(MODEL_MANAGER, msg.data);
|
|
157
|
+
return;
|
|
158
|
+
|
|
169
159
|
case "remove-ui-elements":
|
|
170
160
|
handleRemoveUIElements(msg.data);
|
|
171
161
|
return;
|
|
@@ -1,97 +1,50 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
3
|
|
|
4
|
-
import type { AnyWidget
|
|
5
|
-
import {
|
|
6
|
-
import { useEffect, useMemo, useRef } from "react";
|
|
7
|
-
import useEvent from "react-use-event-hook";
|
|
4
|
+
import type { AnyWidget } from "@anywidget/types";
|
|
5
|
+
import { useEffect, useRef } from "react";
|
|
8
6
|
import { z } from "zod";
|
|
9
|
-
import { MarimoIncomingMessageEvent } from "@/core/dom/events";
|
|
10
7
|
import { asRemoteURL } from "@/core/runtime/config";
|
|
11
8
|
import { resolveVirtualFileURL } from "@/core/static/files";
|
|
12
9
|
import { isStaticNotebook } from "@/core/static/static-state";
|
|
13
10
|
import { useAsyncData } from "@/hooks/useAsyncData";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
type HTMLElementNotDerivedFromRef,
|
|
17
|
-
useEventListener,
|
|
18
|
-
} from "@/hooks/useEventListener";
|
|
11
|
+
import type { HTMLElementNotDerivedFromRef } from "@/hooks/useEventListener";
|
|
19
12
|
import { createPlugin } from "@/plugins/core/builder";
|
|
20
|
-
import { rpc } from "@/plugins/core/rpc";
|
|
21
13
|
import type { IPluginProps } from "@/plugins/types";
|
|
22
|
-
import {
|
|
23
|
-
decodeFromWire,
|
|
24
|
-
isWireFormat,
|
|
25
|
-
serializeBuffersToBase64,
|
|
26
|
-
type WireFormat,
|
|
27
|
-
} from "@/utils/data-views";
|
|
28
14
|
import { prettyError } from "@/utils/errors";
|
|
29
|
-
import type { Base64String } from "@/utils/json/base64";
|
|
30
15
|
import { Logger } from "@/utils/Logger";
|
|
31
16
|
import { ErrorBanner } from "../common/error-banner";
|
|
32
|
-
import { MODEL_MANAGER, Model } from "./model";
|
|
17
|
+
import { getMarimoInternal, MODEL_MANAGER, type Model } from "./model";
|
|
18
|
+
import type { ModelState, WidgetModelId } from "./types";
|
|
33
19
|
|
|
20
|
+
/**
|
|
21
|
+
* AnyWidget asset data
|
|
22
|
+
*/
|
|
34
23
|
interface Data {
|
|
35
24
|
jsUrl: string;
|
|
36
25
|
jsHash: string;
|
|
37
26
|
css?: string | null;
|
|
38
27
|
}
|
|
39
28
|
|
|
40
|
-
type
|
|
41
|
-
|
|
42
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
43
|
-
type PluginFunctions = {
|
|
44
|
-
send_to_widget: <T>(req: {
|
|
45
|
-
content: unknown;
|
|
46
|
-
buffers: Base64String[];
|
|
47
|
-
}) => Promise<null | undefined>;
|
|
48
|
-
};
|
|
29
|
+
type AnyWidgetState = ModelState;
|
|
49
30
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
.withFunctions<PluginFunctions>({
|
|
59
|
-
send_to_widget: rpc
|
|
60
|
-
.input(
|
|
61
|
-
z.object({
|
|
62
|
-
content: z.unknown(),
|
|
63
|
-
buffers: z.array(z.string().transform((v) => v as Base64String)),
|
|
64
|
-
}),
|
|
65
|
-
)
|
|
66
|
-
.output(z.null().optional()),
|
|
67
|
-
})
|
|
68
|
-
.renderer((props) => <AnyWidgetSlot {...props} />);
|
|
69
|
-
|
|
70
|
-
const AnyWidgetSlot = (
|
|
71
|
-
props: IPluginProps<WireFormat<T>, Data, PluginFunctions>,
|
|
72
|
-
) => {
|
|
73
|
-
const { css, jsUrl, jsHash } = props.data;
|
|
31
|
+
/**
|
|
32
|
+
* Initial value is a model_id reference.
|
|
33
|
+
* The backend sends just { model_id: string } and the frontend
|
|
34
|
+
* retrieves the actual state from the 'open' message.
|
|
35
|
+
*/
|
|
36
|
+
interface ModelIdRef {
|
|
37
|
+
model_id: WidgetModelId;
|
|
38
|
+
}
|
|
74
39
|
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
if (isWireFormat(props.value)) {
|
|
78
|
-
const decoded = decodeFromWire(props.value);
|
|
79
|
-
Logger.debug("AnyWidget decoded wire format:", {
|
|
80
|
-
bufferPaths: props.value.bufferPaths,
|
|
81
|
-
buffersCount: props.value.buffers?.length,
|
|
82
|
-
decodedKeys: Object.keys(decoded),
|
|
83
|
-
});
|
|
84
|
-
return decoded;
|
|
85
|
-
}
|
|
86
|
-
Logger.warn("AnyWidget value is not wire format:", props.value);
|
|
87
|
-
return props.value;
|
|
88
|
-
}, [props.value]);
|
|
40
|
+
export function useAnyWidgetModule(opts: { jsUrl: string; jsHash: string }) {
|
|
41
|
+
const { jsUrl, jsHash } = opts;
|
|
89
42
|
|
|
90
43
|
// JS is an ESM file with a render function on it
|
|
91
44
|
// export function render({ model, el }) {
|
|
92
45
|
// ...
|
|
93
46
|
const {
|
|
94
|
-
data:
|
|
47
|
+
data: jsModule,
|
|
95
48
|
error,
|
|
96
49
|
refetch,
|
|
97
50
|
} = useAsyncData(async () => {
|
|
@@ -117,9 +70,16 @@ const AnyWidgetSlot = (
|
|
|
117
70
|
}
|
|
118
71
|
}, [hasError, jsUrl]);
|
|
119
72
|
|
|
73
|
+
return {
|
|
74
|
+
jsModule,
|
|
75
|
+
error,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function useMountCss(css: string | null | undefined, host: HTMLElement) {
|
|
120
80
|
// Mount the CSS
|
|
121
81
|
useEffect(() => {
|
|
122
|
-
const shadowRoot =
|
|
82
|
+
const shadowRoot = host.shadowRoot;
|
|
123
83
|
if (!css || !shadowRoot) {
|
|
124
84
|
return;
|
|
125
85
|
}
|
|
@@ -156,23 +116,38 @@ const AnyWidgetSlot = (
|
|
|
156
116
|
return () => {
|
|
157
117
|
style.remove();
|
|
158
118
|
};
|
|
159
|
-
}, [css,
|
|
119
|
+
}, [css, host]);
|
|
120
|
+
}
|
|
160
121
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
122
|
+
export const AnyWidgetPlugin = createPlugin<ModelIdRef>("marimo-anywidget")
|
|
123
|
+
.withData(
|
|
124
|
+
z.object({
|
|
125
|
+
jsUrl: z.string(),
|
|
126
|
+
jsHash: z.string(),
|
|
127
|
+
css: z.string().nullish(),
|
|
128
|
+
}),
|
|
129
|
+
)
|
|
130
|
+
.withFunctions({})
|
|
131
|
+
.renderer((props) => <AnyWidgetSlot {...props} />);
|
|
132
|
+
|
|
133
|
+
const AnyWidgetSlot = (props: IPluginProps<ModelIdRef, Data>) => {
|
|
134
|
+
const { css, jsUrl, jsHash } = props.data;
|
|
135
|
+
const { model_id: modelId } = props.value;
|
|
136
|
+
const host = props.host as HTMLElementNotDerivedFromRef;
|
|
137
|
+
|
|
138
|
+
const { jsModule, error } = useAnyWidgetModule({ jsUrl, jsHash });
|
|
139
|
+
|
|
140
|
+
useMountCss(css, host);
|
|
166
141
|
|
|
167
142
|
if (error) {
|
|
168
143
|
return <ErrorBanner error={error} />;
|
|
169
144
|
}
|
|
170
145
|
|
|
171
|
-
if (!
|
|
146
|
+
if (!jsModule) {
|
|
172
147
|
return null;
|
|
173
148
|
}
|
|
174
149
|
|
|
175
|
-
if (!isAnyWidgetModule(
|
|
150
|
+
if (!isAnyWidgetModule(jsModule)) {
|
|
176
151
|
const error = new Error(
|
|
177
152
|
`Module at ${jsUrl} does not appear to be a valid anywidget`,
|
|
178
153
|
);
|
|
@@ -189,10 +164,8 @@ const AnyWidgetSlot = (
|
|
|
189
164
|
// Plugins may be stateful and we cannot make assumptions that we won't be
|
|
190
165
|
// so it is safer to just re-render.
|
|
191
166
|
key={key}
|
|
192
|
-
{
|
|
193
|
-
|
|
194
|
-
setValue={wrappedSetValue}
|
|
195
|
-
value={valueWithBuffers}
|
|
167
|
+
widget={jsModule.default}
|
|
168
|
+
modelId={modelId}
|
|
196
169
|
/>
|
|
197
170
|
);
|
|
198
171
|
};
|
|
@@ -200,125 +173,72 @@ const AnyWidgetSlot = (
|
|
|
200
173
|
/**
|
|
201
174
|
* Run the anywidget module
|
|
202
175
|
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
176
|
+
* Per AFM spec (anywidget.dev/en/afm):
|
|
177
|
+
* - initialize() is called once per model lifetime
|
|
178
|
+
* - render() is called once per view (can be multiple per model)
|
|
205
179
|
*/
|
|
206
|
-
async function runAnyWidgetModule(
|
|
207
|
-
widgetDef: AnyWidget
|
|
180
|
+
async function runAnyWidgetModule<T extends AnyWidgetState>(
|
|
181
|
+
widgetDef: AnyWidget<T>,
|
|
208
182
|
model: Model<T>,
|
|
209
183
|
el: HTMLElement,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
invoke: async (_name, _msg, _options) => {
|
|
213
|
-
const message =
|
|
214
|
-
"anywidget.invoke not supported in marimo. Please file an issue at https://github.com/marimo-team/marimo/issues";
|
|
215
|
-
Logger.warn(message);
|
|
216
|
-
throw new Error(message);
|
|
217
|
-
},
|
|
218
|
-
};
|
|
184
|
+
signal: AbortSignal,
|
|
185
|
+
): Promise<void> {
|
|
219
186
|
// Clear the element, in case the widget is re-rendering
|
|
220
187
|
el.innerHTML = "";
|
|
221
|
-
|
|
222
|
-
typeof widgetDef === "function" ? await widgetDef() : widgetDef;
|
|
223
|
-
await widget.initialize?.({ model, experimental });
|
|
188
|
+
|
|
224
189
|
try {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
unsub?.();
|
|
228
|
-
};
|
|
190
|
+
const render = await getMarimoInternal(model).resolveWidget(widgetDef);
|
|
191
|
+
await render(el, signal);
|
|
229
192
|
} catch (error) {
|
|
230
193
|
Logger.error("Error rendering anywidget", error);
|
|
231
194
|
el.classList.add("text-error");
|
|
232
195
|
el.innerHTML = `Error rendering anywidget: ${prettyError(error)}`;
|
|
233
|
-
return () => {
|
|
234
|
-
// No-op
|
|
235
|
-
};
|
|
236
196
|
}
|
|
237
197
|
}
|
|
238
198
|
|
|
239
199
|
function isAnyWidgetModule(mod: any): mod is { default: AnyWidget } {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
mod.default?.render ||
|
|
244
|
-
mod.default?.initialize)
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export function getDirtyFields(value: T, initialValue: T): Set<keyof T> {
|
|
249
|
-
return new Set(
|
|
250
|
-
Object.keys(value).filter((key) => !isEqual(value[key], initialValue[key])),
|
|
251
|
-
);
|
|
252
|
-
}
|
|
200
|
+
if (!mod.default) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
253
203
|
|
|
254
|
-
function hasModelId(message: unknown): message is { model_id: string } {
|
|
255
204
|
return (
|
|
256
|
-
typeof
|
|
205
|
+
typeof mod.default === "function" ||
|
|
206
|
+
typeof mod.default?.render === "function" ||
|
|
207
|
+
typeof mod.default?.initialize === "function"
|
|
257
208
|
);
|
|
258
209
|
}
|
|
259
210
|
|
|
260
|
-
interface Props
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
value: T;
|
|
264
|
-
setValue: (value: Partial<T>) => void;
|
|
211
|
+
interface Props<T extends AnyWidgetState> {
|
|
212
|
+
widget: AnyWidget<T>;
|
|
213
|
+
modelId: WidgetModelId;
|
|
265
214
|
}
|
|
266
215
|
|
|
267
|
-
const LoadedSlot = ({
|
|
268
|
-
value,
|
|
269
|
-
setValue,
|
|
216
|
+
const LoadedSlot = <T extends AnyWidgetState>({
|
|
270
217
|
widget,
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
host,
|
|
274
|
-
}: Props & { widget: AnyWidget }) => {
|
|
218
|
+
modelId,
|
|
219
|
+
}: Props<T> & { widget: AnyWidget<T> }) => {
|
|
275
220
|
const htmlRef = useRef<HTMLDivElement>(null);
|
|
276
221
|
|
|
277
|
-
// value is already decoded from wire format
|
|
278
|
-
const model =
|
|
279
|
-
new Model(value, setValue, functions.send_to_widget, new Set()),
|
|
280
|
-
);
|
|
222
|
+
// value is already decoded from wire format, may be null if waiting for open message
|
|
223
|
+
const model = MODEL_MANAGER.getSync(modelId);
|
|
281
224
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
MarimoIncomingMessageEvent.TYPE,
|
|
286
|
-
(e) => {
|
|
287
|
-
const message = e.detail.message;
|
|
288
|
-
if (hasModelId(message)) {
|
|
289
|
-
MODEL_MANAGER.get(message.model_id).then((model) => {
|
|
290
|
-
model.receiveCustomMessage(message, e.detail.buffers);
|
|
291
|
-
});
|
|
292
|
-
} else {
|
|
293
|
-
model.current.receiveCustomMessage(message, e.detail.buffers);
|
|
294
|
-
}
|
|
295
|
-
},
|
|
296
|
-
);
|
|
225
|
+
if (!model) {
|
|
226
|
+
Logger.error("Model not found for modelId", modelId);
|
|
227
|
+
}
|
|
297
228
|
|
|
298
229
|
useEffect(() => {
|
|
299
|
-
if (!htmlRef.current) {
|
|
230
|
+
if (!htmlRef.current || !model) {
|
|
300
231
|
return;
|
|
301
232
|
}
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
);
|
|
307
|
-
return () => {
|
|
308
|
-
unsubPromise.then((unsub) => unsub());
|
|
309
|
-
};
|
|
310
|
-
// We re-run the widget when the jsUrl changes, which means the cell
|
|
233
|
+
const controller = new AbortController();
|
|
234
|
+
runAnyWidgetModule(widget, model, htmlRef.current, controller.signal);
|
|
235
|
+
return () => controller.abort();
|
|
236
|
+
// We re-run the widget when the modelId changes, which means the cell
|
|
311
237
|
// that created the Widget has been re-run.
|
|
312
238
|
// We need to re-run the widget because it may contain initialization code
|
|
313
239
|
// that could be reset by the new widget.
|
|
314
240
|
// See example: https://github.com/marimo-team/marimo/issues/3962#issuecomment-2703184123
|
|
315
|
-
}, [widget,
|
|
316
|
-
|
|
317
|
-
// When the value changes, update the model
|
|
318
|
-
const valueMemo = useDeepCompareMemoize(value);
|
|
319
|
-
useEffect(() => {
|
|
320
|
-
model.current.updateAndEmitDiffs(valueMemo);
|
|
321
|
-
}, [valueMemo]);
|
|
241
|
+
}, [widget, modelId, model]);
|
|
322
242
|
|
|
323
243
|
return <div ref={htmlRef} />;
|
|
324
244
|
};
|
|
@@ -327,5 +247,4 @@ export const visibleForTesting = {
|
|
|
327
247
|
LoadedSlot,
|
|
328
248
|
runAnyWidgetModule,
|
|
329
249
|
isAnyWidgetModule,
|
|
330
|
-
getDirtyFields,
|
|
331
250
|
};
|