@marimo-team/islands 0.20.3-dev79 → 0.20.3-dev82

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/main.js CHANGED
@@ -70347,7 +70347,7 @@ Image URL: ${r.imageUrl}`)), contextToXml({
70347
70347
  return Logger.warn("Failed to get version from mount config"), null;
70348
70348
  }
70349
70349
  }
70350
- const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.20.3-dev79"), showCodeInRunModeAtom = atom(true);
70350
+ const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.20.3-dev82"), showCodeInRunModeAtom = atom(true);
70351
70351
  atom(null);
70352
70352
  var import_compiler_runtime$88 = require_compiler_runtime();
70353
70353
  function useKeydownOnElement(e, r) {
@@ -78866,7 +78866,7 @@ ${c}
78866
78866
  resizeHandleEnabled: "data-panel-resize-handle-enabled",
78867
78867
  resizeHandleId: "data-panel-resize-handle-id",
78868
78868
  resizeHandleState: "data-resize-handle-state"
78869
- }, PRECISION = 10, useIsomorphicLayoutEffect$1 = import_react.useLayoutEffect, useId$8 = import_react.useId, wrappedUseId = typeof useId$8 == "function" ? useId$8 : () => null, counter = 0;
78869
+ }, PRECISION = 10, useIsomorphicLayoutEffect = import_react.useLayoutEffect, useId$8 = import_react.useId, wrappedUseId = typeof useId$8 == "function" ? useId$8 : () => null, counter = 0;
78870
78870
  function useUniqueId(e = null) {
78871
78871
  let r = wrappedUseId(), c = (0, import_react.useRef)(e || r || null);
78872
78872
  return c.current === null && (c.current = "" + counter++), e ?? c.current;
@@ -78893,12 +78893,12 @@ ${c}
78893
78893
  });
78894
78894
  (0, import_react.useRef)({
78895
78895
  didLogMissingDefaultSizeWarning: false
78896
- }), useIsomorphicLayoutEffect$1(() => {
78896
+ }), useIsomorphicLayoutEffect(() => {
78897
78897
  let { callbacks: e2, constraints: r2 } = qY.current, _2 = {
78898
78898
  ...r2
78899
78899
  };
78900
78900
  qY.current.id = KY, qY.current.idIsFromProps = v !== void 0, qY.current.order = M, e2.onCollapse = w, e2.onExpand = E, e2.onResize = O, r2.collapsedSize = c, r2.collapsible = d, r2.defaultSize = f, r2.maxSize = y, r2.minSize = S, (_2.collapsedSize !== r2.collapsedSize || _2.collapsible !== r2.collapsible || _2.maxSize !== r2.maxSize || _2.minSize !== r2.minSize) && HY(qY.current, _2);
78901
- }), useIsomorphicLayoutEffect$1(() => {
78901
+ }), useIsomorphicLayoutEffect(() => {
78902
78902
  let e2 = qY.current;
78903
78903
  return UY(e2), () => {
78904
78904
  GY(e2);
@@ -79364,7 +79364,7 @@ ${c}
79364
79364
  function useWindowSplitterPanelGroupBehavior({ committedValuesRef: e, eagerValuesRef: r, groupId: c, layout: d, panelDataArray: f, panelGroupElement: _, setLayout: v }) {
79365
79365
  (0, import_react.useRef)({
79366
79366
  didWarnAboutMissingResizeHandle: false
79367
- }), useIsomorphicLayoutEffect$1(() => {
79367
+ }), useIsomorphicLayoutEffect(() => {
79368
79368
  if (!_) return;
79369
79369
  let e2 = getResizeHandleElementsForGroup(c, _);
79370
79370
  for (let r2 = 0; r2 < f.length - 1; r2++) {
@@ -79662,7 +79662,7 @@ ${c}
79662
79662
  });
79663
79663
  areEqual(c2, f2) || (IY(f2), HY.current.layout = f2, r2 && r2(f2), callPanelCallbacks(d2, f2, RY.current));
79664
79664
  }
79665
- }), []), useIsomorphicLayoutEffect$1(() => {
79665
+ }), []), useIsomorphicLayoutEffect(() => {
79666
79666
  VY.current.autoSaveId = e, VY.current.direction = d, VY.current.dragState = z, VY.current.id = M, VY.current.onLayout = v, VY.current.storage = S;
79667
79667
  }), useWindowSplitterPanelGroupBehavior({
79668
79668
  committedValuesRef: VY,
@@ -79752,7 +79752,7 @@ ${c}
79752
79752
  }, [
79753
79753
  LY
79754
79754
  ]);
79755
- useIsomorphicLayoutEffect$1(() => {
79755
+ useIsomorphicLayoutEffect(() => {
79756
79756
  if (HY.current.panelDataArrayChanged) {
79757
79757
  HY.current.panelDataArrayChanged = false;
79758
79758
  let { autoSaveId: e2, onLayout: r2, storage: c2 } = VY.current, { layout: d2, panelDataArray: f2 } = HY.current, _2 = null;
@@ -79769,7 +79769,7 @@ ${c}
79769
79769
  });
79770
79770
  areEqual(d2, v2) || (IY(v2), HY.current.layout = v2, r2 && r2(v2), callPanelCallbacks(f2, v2, RY.current));
79771
79771
  }
79772
- }), useIsomorphicLayoutEffect$1(() => {
79772
+ }), useIsomorphicLayoutEffect(() => {
79773
79773
  let e2 = HY.current;
79774
79774
  return () => {
79775
79775
  e2.layout = [];
@@ -79960,7 +79960,7 @@ ${c}
79960
79960
  let { direction: LY, groupId: RY, registerResizeHandle: zY, startDragging: BY, stopDragging: VY, panelGroupElement: HY } = IY, UY = useUniqueId(f), [WY, GY] = (0, import_react.useState)("inactive"), [KY, qY] = (0, import_react.useState)(false), [JY, YY] = (0, import_react.useState)(null), XY = (0, import_react.useRef)({
79961
79961
  state: WY
79962
79962
  });
79963
- useIsomorphicLayoutEffect$1(() => {
79963
+ useIsomorphicLayoutEffect(() => {
79964
79964
  XY.current.state = WY;
79965
79965
  }), (0, import_react.useEffect)(() => {
79966
79966
  if (c) YY(null);
@@ -91520,18 +91520,6 @@ ${c}
91520
91520
  function withoutNaN(e) {
91521
91521
  return e == null || Number.isNaN(e) ? null : e;
91522
91522
  }
91523
- var useIsomorphicLayoutEffect = typeof window < "u" && window.document !== void 0 && window.document.createElement !== void 0 ? import_react.useLayoutEffect : import_react.useEffect;
91524
- function useEvent(e) {
91525
- let r = (0, import_react.useRef)(e);
91526
- return useIsomorphicLayoutEffect(() => {
91527
- r.current = e;
91528
- }), (0, import_react.useCallback)(function() {
91529
- var e2 = [
91530
- ...arguments
91531
- ];
91532
- return r.current == null ? void 0 : r.current(...e2);
91533
- }, []);
91534
- }
91535
91523
  const MessageSchema = object$1({
91536
91524
  content: union([
91537
91525
  object$1({
@@ -91585,10 +91573,8 @@ ${c}
91585
91573
  this.buffer = [];
91586
91574
  }
91587
91575
  getAndClear() {
91588
- let e = [
91589
- ...this.buffer
91590
- ];
91591
- return this.clear(), e;
91576
+ let e = this.buffer;
91577
+ return this.buffer = [], e;
91592
91578
  }
91593
91579
  };
91594
91580
  const PanelPlugin = createPlugin("marimo-panel").withData(object$1({
@@ -91609,16 +91595,22 @@ ${c}
91609
91595
  return window.Bokeh != null;
91610
91596
  }
91611
91597
  var PanelSlot = (e) => {
91612
- let { data: r, functions: c, host: d } = e, { extension: f, docs_json: _, render_json: v } = r, y = (0, import_react.useRef)(null), S = (0, import_react.useRef)(null), w = (0, import_react.useRef)(null), [E, O] = (0, import_react.useState)(false), [M, I] = (0, import_react.useState)(null), z = useEvent(() => {
91613
- if (!G.current || !S.current) return;
91614
- let e2 = G.current.getAndClear(), r2 = S.current.create_json_patch(e2), d2 = {
91615
- ...window.Bokeh.protocol.Message.create("PATCH-DOC", {}, r2)
91616
- }, f2 = [];
91617
- d2.content = extractBuffers(d2.content, f2), c.send_to_widget({
91618
- message: d2,
91619
- buffers: f2
91620
- });
91621
- }), G = (0, import_react.useRef)(new EventBuffer(z));
91598
+ let { data: r, functions: c, host: d } = e, { extension: f, docs_json: _, render_json: v } = r, y = (0, import_react.useRef)(null), S = (0, import_react.useRef)(null), w = (0, import_react.useRef)(null), [E, O] = (0, import_react.useState)(false), [M, I] = (0, import_react.useState)(null), z = (0, import_react.useRef)(c);
91599
+ z.current = c;
91600
+ let G = (0, import_react.useCallback)(() => {
91601
+ if (!q.current) return;
91602
+ let e2 = q.current.getAndClear();
91603
+ if (e2.length === 0) return;
91604
+ let r2 = e2.at(0);
91605
+ if (!isDocumentEvent(r2)) return;
91606
+ let c2 = r2.document, d2 = e2.filter((e3) => isDocumentEvent(e3) && e3.document === c2), f2 = c2.create_json_patch(d2), _2 = window.Bokeh.protocol.Message.create("PATCH-DOC", {}, f2), v2 = [];
91607
+ _2.content = extractBuffers(_2.content, v2), z.current.send_to_widget({
91608
+ message: _2,
91609
+ buffers: v2
91610
+ }).catch((e3) => {
91611
+ Logger.warn("Failed to send Panel event to backend", e3);
91612
+ });
91613
+ }, []), q = (0, import_react.useRef)(new EventBuffer(G));
91622
91614
  return (0, import_react.useEffect)(() => {
91623
91615
  if (isBokehLoaded()) {
91624
91616
  O(true);
@@ -91637,20 +91629,25 @@ ${c}
91637
91629
  O
91638
91630
  ]), useEventListener(d, MarimoIncomingMessageEvent.TYPE, (e2) => {
91639
91631
  if (e2.detail.message == null) return;
91640
- let r2 = MessageSchema.parse(e2.detail.message), c2 = e2.detail.buffers, d2 = w.current, f2 = S.current;
91641
- if (!d2 || !f2) return;
91642
- let _2 = r2.content;
91643
- if (_2 !== null && typeof r2.content != "string") {
91644
- G.current && G.current.size() > 0 && z();
91632
+ let r2 = MessageSchema.parse(e2.detail.message), c2 = e2.detail.buffers, d2 = w.current;
91633
+ if (!d2) return;
91634
+ let f2 = r2.content;
91635
+ if (f2 !== null && typeof r2.content != "string") {
91636
+ q.current && q.current.size() > 0 && G();
91645
91637
  return;
91646
91638
  }
91639
+ let _2 = getDoc(S.current);
91640
+ if (!_2) return;
91647
91641
  if (c2 && c2.length > 0) {
91648
91642
  let e3 = c2[0];
91649
91643
  e3 instanceof ArrayBuffer ? d2.consume(e3) : e3.buffer instanceof ArrayBuffer && d2.consume(e3.buffer);
91650
- } else if (_2 && typeof _2 == "string") d2.consume(_2);
91644
+ } else if (f2 && typeof f2 == "string") d2.consume(f2);
91651
91645
  else return;
91652
91646
  let v2 = d2.message;
91653
- v2 != null && Object.keys(v2.content).length > 0 && (v2.content.events !== void 0 && (v2.content.events = v2.content.events.filter((e3) => f2._all_models.has(e3.model.id))), f2.apply_json_patch(v2.content, v2.buffers));
91647
+ v2 != null && Object.keys(v2.content).length > 0 && (v2.content.events !== void 0 && (v2.content.events = v2.content.events.filter((e3) => {
91648
+ var _a3;
91649
+ return ((_a3 = e3 == null ? void 0 : e3.model) == null ? void 0 : _a3.id) && _2._all_models.has(e3.model.id);
91650
+ })), _2.apply_json_patch(v2.content, v2.buffers));
91654
91651
  }), (0, import_react.useEffect)(() => {
91655
91652
  let e2 = Object.keys(_)[0];
91656
91653
  !E || M === e2 || (async () => {
@@ -91661,16 +91658,18 @@ ${c}
91661
91658
  for (let e3 of Object.keys(v.roots)) {
91662
91659
  let c3 = v.roots[e3];
91663
91660
  if (y.current) {
91664
- let d2 = y.current.querySelector(`#${c3}`);
91665
- r2.roots[e3] = d2;
91661
+ let d3 = y.current.querySelector(`#${c3}`);
91662
+ r2.roots[e3] = d3;
91666
91663
  }
91667
91664
  }
91668
91665
  let c2 = Object.keys(r2.roots)[0];
91669
91666
  await window.Bokeh.embed.embed_items_notebook(_, [
91670
91667
  r2
91671
- ]), S.current = window.Bokeh.index.get_by_id(c2).model.document, w.current = new window.Bokeh.protocol.Receiver(), S.current.on_change((e3) => {
91668
+ ]), S.current = c2;
91669
+ let d2 = window.Bokeh.index.get_by_id(c2).model.document;
91670
+ w.current = new window.Bokeh.protocol.Receiver(), d2.on_change((e3) => {
91672
91671
  var _a3;
91673
- return (_a3 = G.current) == null ? void 0 : _a3.add(e3);
91672
+ isSyncEvent(e3) && e3.sync && ((_a3 = q.current) == null ? void 0 : _a3.add(e3));
91674
91673
  }), I(e2);
91675
91674
  })();
91676
91675
  }, [
@@ -91690,7 +91689,22 @@ ${c}
91690
91689
  })
91691
91690
  }, e2))
91692
91691
  });
91693
- }, defaultOptions = {
91692
+ };
91693
+ function getDoc(e) {
91694
+ if (!e) return null;
91695
+ try {
91696
+ return window.Bokeh.index.get_by_id(e).model.document;
91697
+ } catch (e2) {
91698
+ return Logger.warn("Failed to get Bokeh document", e2), null;
91699
+ }
91700
+ }
91701
+ function isSyncEvent(e) {
91702
+ return !!e && typeof e == "object" && "sync" in e;
91703
+ }
91704
+ function isDocumentEvent(e) {
91705
+ return !!e && typeof e == "object" && "document" in e;
91706
+ }
91707
+ var defaultOptions = {
91694
91708
  removeOnUnmount: false
91695
91709
  };
91696
91710
  function useScript(e, r = defaultOptions) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.20.3-dev79",
3
+ "version": "0.20.3-dev82",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -1,8 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  /* eslint-disable @typescript-eslint/no-explicit-any */
3
3
 
4
- import { useEvent } from "@dnd-kit/utilities";
5
- import { useEffect, useRef, useState } from "react";
4
+ import { useCallback, useEffect, useRef, useState } from "react";
6
5
  import { z } from "zod";
7
6
  import { MarimoIncomingMessageEvent } from "@/core/dom/events";
8
7
  import {
@@ -12,13 +11,14 @@ import {
12
11
  import { createPlugin } from "@/plugins/core/builder";
13
12
  import { rpc } from "@/plugins/core/rpc";
14
13
  import type { IPluginProps } from "@/plugins/types";
14
+ import { Logger } from "@/utils/Logger";
15
15
  import { EventBuffer, extractBuffers, MessageSchema } from "./utils";
16
16
 
17
17
  interface BokehDocument {
18
18
  create_json_patch: (events: unknown[]) => unknown;
19
19
  apply_json_patch: (content: unknown, buffers: ArrayBuffer[]) => void;
20
20
  on_change: (callback: (event: unknown) => void) => void;
21
- _all_models: Set<string>;
21
+ _all_models: Map<string, unknown>;
22
22
  }
23
23
 
24
24
  interface RenderItems {
@@ -77,8 +77,8 @@ type T = Record<string, unknown>;
77
77
  // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
78
78
  type PluginFunctions = {
79
79
  send_to_widget: <T>(req: {
80
- message?: any;
81
- buffers?: any;
80
+ message?: unknown;
81
+ buffers?: unknown;
82
82
  }) => Promise<null | undefined>;
83
83
  };
84
84
 
@@ -114,27 +114,52 @@ const PanelSlot = (props: Props) => {
114
114
  const { data, functions, host } = props;
115
115
  const { extension, docs_json: docsJson, render_json: renderJson } = data;
116
116
  const ref = useRef<HTMLDivElement>(null);
117
- const docRef = useRef<BokehDocument | null>(null);
117
+ const rootModelIdRef = useRef<string | null>(null);
118
118
  const receiverRef = useRef<InstanceType<
119
119
  typeof window.Bokeh.protocol.Receiver
120
120
  > | null>(null);
121
121
  const [loaded, setLoaded] = useState<boolean>(false);
122
122
  const [rendered, setRendered] = useState<string | null>(null);
123
123
 
124
- const processEvents = useEvent(() => {
125
- if (!eventBufferRef.current || !docRef.current) {
124
+ // Store functions in a ref so the callback captured by EventBuffer stays current
125
+ const functionsRef = useRef(functions);
126
+ functionsRef.current = functions;
127
+
128
+ const processEvents = useCallback(() => {
129
+ if (!eventBufferRef.current) {
126
130
  return;
127
131
  }
128
132
 
129
133
  const events = eventBufferRef.current.getAndClear();
130
- const patch = docRef.current.create_json_patch(events);
131
- const message = {
132
- ...window.Bokeh.protocol.Message.create("PATCH-DOC", {}, patch),
133
- };
134
+ if (events.length === 0) {
135
+ return;
136
+ }
137
+
138
+ // Use the event's own document — it is always current, even when
139
+ // DynamicMap/HoloViews has replaced the document after embedding.
140
+ const firstEvent = events.at(0);
141
+ if (!isDocumentEvent(firstEvent)) {
142
+ return;
143
+ }
144
+ const doc = firstEvent.document;
145
+
146
+ // Keep only events that belong to this document
147
+ const sameDocEvents = events.filter(
148
+ (ev) => isDocumentEvent(ev) && ev.document === doc,
149
+ );
150
+
151
+ const patch = doc.create_json_patch(sameDocEvents);
152
+ const message = window.Bokeh.protocol.Message.create(
153
+ "PATCH-DOC",
154
+ {},
155
+ patch,
156
+ );
134
157
  const buffers: ArrayBuffer[] = [];
135
158
  message.content = extractBuffers(message.content, buffers);
136
- functions.send_to_widget({ message, buffers });
137
- });
159
+ functionsRef.current.send_to_widget({ message, buffers }).catch((error) => {
160
+ Logger.warn("Failed to send Panel event to backend", error);
161
+ });
162
+ }, []);
138
163
 
139
164
  const eventBufferRef = useRef<EventBuffer<unknown> | null>(
140
165
  new EventBuffer(processEvents),
@@ -178,9 +203,8 @@ const PanelSlot = (props: Props) => {
178
203
  const message = MessageSchema.parse(e.detail.message);
179
204
  const buffers = e.detail.buffers;
180
205
  const receiver = receiverRef.current;
181
- const doc = docRef.current;
182
206
 
183
- if (!receiver || !doc) {
207
+ if (!receiver) {
184
208
  return;
185
209
  }
186
210
 
@@ -193,6 +217,11 @@ const PanelSlot = (props: Props) => {
193
217
  return;
194
218
  }
195
219
 
220
+ const doc = getDoc(rootModelIdRef.current);
221
+ if (!doc) {
222
+ return;
223
+ }
224
+
196
225
  if (buffers && buffers.length > 0) {
197
226
  // Check if we already have an ArrayBuffer
198
227
  const buffer = buffers[0];
@@ -212,7 +241,7 @@ const PanelSlot = (props: Props) => {
212
241
  if (commMessage != null && Object.keys(commMessage.content).length > 0) {
213
242
  if (commMessage.content.events !== undefined) {
214
243
  commMessage.content.events = commMessage.content.events.filter(
215
- (e: any) => doc._all_models.has(e.model.id),
244
+ (e) => e?.model?.id && doc._all_models.has(e.model.id),
216
245
  );
217
246
  }
218
247
  doc.apply_json_patch(commMessage.content, commMessage.buffers);
@@ -243,9 +272,17 @@ const PanelSlot = (props: Props) => {
243
272
  }
244
273
  const modelId = Object.keys(renderItem.roots)[0];
245
274
  await window.Bokeh.embed.embed_items_notebook(docsJson, [renderItem]);
246
- docRef.current = window.Bokeh.index.get_by_id(modelId).model.document;
275
+ rootModelIdRef.current = modelId;
276
+ const doc = window.Bokeh.index.get_by_id(modelId).model.document;
247
277
  receiverRef.current = new window.Bokeh.protocol.Receiver();
248
- docRef.current.on_change((event) => eventBufferRef.current?.add(event));
278
+ doc.on_change((event) => {
279
+ // Bokeh tags server-applied patch events with sync=false via
280
+ // model.setv({...}, {sync: false}) inside apply_json_patch.
281
+ // Only forward user-initiated events (sync=true, the default).
282
+ if (isSyncEvent(event) && event.sync) {
283
+ eventBufferRef.current?.add(event);
284
+ }
285
+ });
249
286
  setRendered(docId);
250
287
  };
251
288
 
@@ -263,4 +300,29 @@ const PanelSlot = (props: Props) => {
263
300
  );
264
301
  };
265
302
 
303
+ // Re-fetch the live document from the Bokeh index. DynamicMap/HoloViews
304
+ // may replace the document after initial embedding, so a stale ref would
305
+ // cause "Cannot create a patch using events from a different document".
306
+ function getDoc(id: string | null): BokehDocument | null {
307
+ if (!id) {
308
+ return null;
309
+ }
310
+ try {
311
+ return window.Bokeh.index.get_by_id(id).model.document;
312
+ } catch (error) {
313
+ Logger.warn("Failed to get Bokeh document", error);
314
+ return null;
315
+ }
316
+ }
317
+
318
+ function isSyncEvent(x: unknown): x is { sync: boolean } {
319
+ const isObject = !!x && typeof x === "object";
320
+ return isObject && "sync" in x;
321
+ }
322
+
323
+ function isDocumentEvent(x: unknown): x is { document: BokehDocument } {
324
+ const isObject = !!x && typeof x === "object";
325
+ return isObject && "document" in x;
326
+ }
327
+
266
328
  type Props = IPluginProps<T, PanelData, PluginFunctions>;
@@ -99,8 +99,8 @@ export class EventBuffer<T> {
99
99
  }
100
100
 
101
101
  getAndClear() {
102
- const events = [...this.buffer];
103
- this.clear();
102
+ const events = this.buffer;
103
+ this.buffer = [];
104
104
  return events;
105
105
  }
106
106
  }