@marimo-team/islands 0.20.5-dev74 → 0.20.5-dev76

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
@@ -11701,6 +11701,7 @@ ${d.join("\n")}`;
11701
11701
  function sanitizeBigInt(e) {
11702
11702
  return typeof e == "object" && e && "$bigint" in e && typeof e.$bigint == "string" ? BigInt(e.$bigint) : e;
11703
11703
  }
11704
+ const OBJECT_ID_ATTR = "object-id", RANDOM_ID_ATTR = "random-id";
11704
11705
  var lowercase = "abcdefghijklmnopqrstuvwxyz", alphabet = lowercase + lowercase.toUpperCase(), seen = /* @__PURE__ */ new Set();
11705
11706
  const SCRATCH_CELL_ID = "__scratch__", SETUP_CELL_ID = "setup", CellId = {
11706
11707
  create() {
@@ -11740,7 +11741,7 @@ ${d.join("\n")}`;
11740
11741
  }
11741
11742
  const UIElementId = {
11742
11743
  parse(e) {
11743
- return e.getAttribute("object-id");
11744
+ return e.getAttribute(OBJECT_ID_ATTR);
11744
11745
  },
11745
11746
  parseOrThrow(e) {
11746
11747
  let r = UIElementId.parse(e);
@@ -61588,7 +61589,7 @@ ${O}`,
61588
61589
  }
61589
61590
  static get observedAttributes() {
61590
61591
  return [
61591
- "random-id"
61592
+ RANDOM_ID_ATTR
61592
61593
  ];
61593
61594
  }
61594
61595
  attributeChangedCallback(e2, r, c) {
@@ -61969,7 +61970,7 @@ ${O}`,
61969
61970
  }), r[7] = f2, r[8] = _2), _2;
61970
61971
  }
61971
61972
  let w;
61972
- r[9] === e.host ? w = r[10] : (w = (_a3 = e.host.closest("[random-id]")) == null ? void 0 : _a3.getAttribute("random-id"), r[9] = e.host, r[10] = w);
61973
+ r[9] === e.host ? w = r[10] : (w = (_a3 = e.host.closest(`[${RANDOM_ID_ATTR}]`)) == null ? void 0 : _a3.getAttribute(RANDOM_ID_ATTR), r[9] = e.host, r[10] = w);
61973
61974
  let E = w ?? c, O;
61974
61975
  return r[11] !== _ || r[12] !== y.default || r[13] !== E || r[14] !== f ? (O = (0, import_jsx_runtime.jsx)(LoadedSlot, {
61975
61976
  widget: y.default,
@@ -70719,7 +70720,7 @@ Image URL: ${r.imageUrl}`)), contextToXml({
70719
70720
  return Logger.warn("Failed to get version from mount config"), null;
70720
70721
  }
70721
70722
  }
70722
- const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.20.5-dev74"), showCodeInRunModeAtom = atom(true);
70723
+ const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.20.5-dev76"), showCodeInRunModeAtom = atom(true);
70723
70724
  atom(null);
70724
70725
  var import_compiler_runtime$89 = require_compiler_runtime();
70725
70726
  function useKeydownOnElement(e, r) {
@@ -81049,6 +81050,7 @@ ${c}
81049
81050
  children: (0, import_jsx_runtime.jsx)(LazyDataTableComponent, {
81050
81051
  isLazy: e.data.lazy,
81051
81052
  preload: e.data.preload,
81053
+ host: e.host,
81052
81054
  children: (0, import_jsx_runtime.jsx)(LoadingDataTableComponent, {
81053
81055
  ...e.data,
81054
81056
  ...e.functions,
@@ -81061,24 +81063,43 @@ ${c}
81061
81063
  })
81062
81064
  })
81063
81065
  }));
81066
+ var previewedTables = /* @__PURE__ */ new Map();
81067
+ function wasTablePreviewed(e, r) {
81068
+ return e != null && r != null && previewedTables.get(e) === r;
81069
+ }
81070
+ function markTablePreviewed(e, r) {
81071
+ e != null && r != null && previewedTables.set(e, r);
81072
+ }
81064
81073
  var LazyDataTableComponent = (e) => {
81065
- let r = (0, import_compiler_runtime$58.c)(1), { isLazy: c, children: d, preload: f } = e, [_, v] = (0, import_react.useState)(c && !f);
81066
- if (_) {
81074
+ var _a3;
81075
+ let r = (0, import_compiler_runtime$58.c)(13), { isLazy: c, children: d, preload: f, host: _ } = e, v, y, S, w;
81076
+ if (r[0] !== _ || r[1] !== c || r[2] !== f) {
81077
+ let e2 = _.closest(`[${OBJECT_ID_ATTR}]`);
81078
+ w = e2 ? UIElementId.parse(e2) : null, v = (_a3 = _.closest(`[${RANDOM_ID_ATTR}]`)) == null ? void 0 : _a3.getAttribute(RANDOM_ID_ATTR), y = import_react.useState, S = c && !f && !wasTablePreviewed(w, v), r[0] = _, r[1] = c, r[2] = f, r[3] = v, r[4] = y, r[5] = S, r[6] = w;
81079
+ } else v = r[3], y = r[4], S = r[5], w = r[6];
81080
+ let [E, O] = y(S);
81081
+ if (E) {
81067
81082
  let e2;
81068
- return r[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (e2 = (0, import_jsx_runtime.jsx)("div", {
81083
+ r[7] !== v || r[8] !== w ? (e2 = () => {
81084
+ markTablePreviewed(w, v), O(false);
81085
+ }, r[7] = v, r[8] = w, r[9] = e2) : e2 = r[9];
81086
+ let c2;
81087
+ r[10] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (c2 = (0, import_jsx_runtime.jsx)(Table2, {
81088
+ className: "mr-2 h-4 w-4"
81089
+ }), r[10] = c2) : c2 = r[10];
81090
+ let d2;
81091
+ return r[11] === e2 ? d2 = r[12] : (d2 = (0, import_jsx_runtime.jsx)("div", {
81069
81092
  className: "flex h-20 items-center justify-center",
81070
81093
  children: (0, import_jsx_runtime.jsxs)(Button, {
81071
81094
  variant: "outline",
81072
81095
  size: "xs",
81073
- onClick: () => v(false),
81096
+ onClick: e2,
81074
81097
  children: [
81075
- (0, import_jsx_runtime.jsx)(Table2, {
81076
- className: "mr-2 h-4 w-4"
81077
- }),
81098
+ c2,
81078
81099
  "Preview data"
81079
81100
  ]
81080
81101
  })
81081
- }), r[0] = e2) : e2 = r[0], e2;
81102
+ }), r[11] = e2, r[12] = d2), d2;
81082
81103
  }
81083
81104
  return d;
81084
81105
  };
@@ -84467,7 +84488,7 @@ ${c}
84467
84488
  var PARENT_DIRECTORY = "..";
84468
84489
  const FileBrowser = ({ value: e, setValue: r, initialPath: c, selectionMode: d, multiple: f, label: _, restrictNavigation: v, list_directory: y, host: S }) => {
84469
84490
  var _a3;
84470
- let [E, O] = useInternalStateWithSync(c), [M, I] = (0, import_react.useState)("Select all"), [z, G] = (0, import_react.useState)(false), [q, IY] = (0, import_react.useState)(false), LY = (_a3 = S.closest("[random-id]")) == null ? void 0 : _a3.getAttribute("random-id"), { data: RY, error: zY, isPending: BY } = useAsyncData(() => y({
84491
+ let [E, O] = useInternalStateWithSync(c), [M, I] = (0, import_react.useState)("Select all"), [z, G] = (0, import_react.useState)(false), [q, IY] = (0, import_react.useState)(false), LY = (_a3 = S.closest(`[${RANDOM_ID_ATTR}]`)) == null ? void 0 : _a3.getAttribute(RANDOM_ID_ATTR), { data: RY, error: zY, isPending: BY } = useAsyncData(() => y({
84471
84492
  path: E
84472
84493
  }), [
84473
84494
  E,
@@ -101391,7 +101412,7 @@ ${c}
101391
101412
  return extractIslandCodeFromEmbed(this);
101392
101413
  }
101393
101414
  connectedCallback() {
101394
- let r = this.querySelectorOrThrow(_l2.outputTagName).innerHTML, c = this.getOptionalEditor(), d = this.code, f = c ? () => `${UI_ELEMENT_REGISTRY.lookupValue(c.props["object-id"])}` : () => d;
101415
+ let r = this.querySelectorOrThrow(_l2.outputTagName).innerHTML, c = this.getOptionalEditor(), d = this.code, f = c ? () => `${UI_ELEMENT_REGISTRY.lookupValue(c.props[OBJECT_ID_ATTR])}` : () => d;
101395
101416
  this.root = import_client.createRoot(this), this.render(r, f, c);
101396
101417
  }
101397
101418
  render(e, r, c) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.20.5-dev74",
3
+ "version": "0.20.5-dev76",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -1,6 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  /* eslint-disable @typescript-eslint/no-redeclare */
3
3
 
4
+ import { OBJECT_ID_ATTR } from "@/core/dom/ui-element-constants";
4
5
  import { invariant } from "@/utils/invariant";
5
6
  import type { TypedString } from "../../utils/typed";
6
7
 
@@ -110,7 +111,7 @@ export function findCellId(element: HTMLElement): CellId | null {
110
111
  export type UIElementId = `${CellId}-${string}`;
111
112
  export const UIElementId = {
112
113
  parse(element: Element): UIElementId | null {
113
- return element.getAttribute("object-id") as UIElementId | null;
114
+ return element.getAttribute(OBJECT_ID_ATTR) as UIElementId | null;
114
115
  },
115
116
  parseOrThrow(element: Element): UIElementId {
116
117
  const id = UIElementId.parse(element);
@@ -0,0 +1,15 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ /**
4
+ * Stable identifier for a UI element, deterministic across re-executions
5
+ * of the same cell (based on cell ID + creation order).
6
+ * Used to synchronize multiple instances and kernel state.
7
+ */
8
+ export const OBJECT_ID_ATTR = "object-id";
9
+
10
+ /**
11
+ * Random token that changes every time a UI element is constructed
12
+ * (i.e., every cell execution). Used to detect stale elements and
13
+ * force re-renders when a cell re-runs.
14
+ */
15
+ export const RANDOM_ID_ATTR = "random-id";
@@ -8,6 +8,7 @@ import {
8
8
  MarimoValueInputEvent,
9
9
  type MarimoValueInputEventType,
10
10
  } from "./events";
11
+ import { RANDOM_ID_ATTR } from "./ui-element-constants";
11
12
  import { UI_ELEMENT_REGISTRY } from "./uiregistry";
12
13
 
13
14
  import "./ui-element.css";
@@ -189,7 +190,7 @@ export function initializeUIElement() {
189
190
  // used like a React key. If the random-id changes, we need to unmount and
190
191
  // remount its child.
191
192
  static get observedAttributes() {
192
- return ["random-id"];
193
+ return [RANDOM_ID_ATTR];
193
194
  }
194
195
 
195
196
  attributeChangedCallback(
@@ -199,7 +200,7 @@ export function initializeUIElement() {
199
200
  ) {
200
201
  if (this.initialized) {
201
202
  const hasChanged = oldValue !== newValue;
202
- if (name === "random-id" && hasChanged) {
203
+ if (name === RANDOM_ID_ATTR && hasChanged) {
203
204
  // deregister/clean-up this instance
204
205
  this.disconnectedCallback();
205
206
  // remove and re-add its child to force it to re-render; note that
@@ -6,6 +6,7 @@ import ReactDOM, { type Root } from "react-dom/client";
6
6
  import { ErrorBoundary } from "@/components/editor/boundary/ErrorBoundary";
7
7
  import { TooltipProvider } from "@/components/ui/tooltip";
8
8
  import { notebookAtom } from "@/core/cells/cells";
9
+ import { OBJECT_ID_ATTR } from "@/core/dom/ui-element-constants";
9
10
  import { UI_ELEMENT_REGISTRY } from "@/core/dom/uiregistry";
10
11
  import { LocaleProvider } from "@/core/i18n/locale-provider";
11
12
  import { renderHTML } from "@/plugins/core/RenderHTML";
@@ -60,7 +61,7 @@ export class MarimoIslandElement extends HTMLElement {
60
61
  const codeCallback: () => string = optionalEditor
61
62
  ? () =>
62
63
  `${UI_ELEMENT_REGISTRY.lookupValue(
63
- optionalEditor.props["object-id"],
64
+ optionalEditor.props[OBJECT_ID_ATTR],
64
65
  )}`
65
66
  : () => code;
66
67
 
@@ -60,7 +60,11 @@ import { ContextAwarePanelItem } from "@/components/editor/chrome/panels/context
60
60
  import { Alert, AlertTitle } from "@/components/ui/alert";
61
61
  import { Button } from "@/components/ui/button";
62
62
  import { DelayMount } from "@/components/utils/delay-mount";
63
- import { type CellId, findCellId } from "@/core/cells/ids";
63
+ import { type CellId, findCellId, UIElementId } from "@/core/cells/ids";
64
+ import {
65
+ OBJECT_ID_ATTR,
66
+ RANDOM_ID_ATTR,
67
+ } from "@/core/dom/ui-element-constants";
64
68
  import { slotsController } from "@/core/slots/slots";
65
69
  import { store } from "@/core/state/jotai";
66
70
  import { isStaticNotebook } from "@/core/static/static-state";
@@ -361,6 +365,7 @@ export const DataTablePlugin = createPlugin<S>("marimo-table")
361
365
  <LazyDataTableComponent
362
366
  isLazy={props.data.lazy}
363
367
  preload={props.data.preload}
368
+ host={props.host}
364
369
  >
365
370
  <LoadingDataTableComponent
366
371
  {...props.data}
@@ -377,21 +382,66 @@ export const DataTablePlugin = createPlugin<S>("marimo-table")
377
382
  );
378
383
  });
379
384
 
385
+ /**
386
+ * Tracks which lazy tables have been previewed across remounts (e.g. tab switches).
387
+ * Keyed by uiElementId (stable across remounts) with randomId as value
388
+ * (changes on cell re-execution, so stale entries are naturally invalidated).
389
+ */
390
+ const previewedTables = new Map<UIElementId, string>();
391
+
392
+ function wasTablePreviewed(
393
+ uiElementId: UIElementId | null | undefined,
394
+ randomId: string | null | undefined,
395
+ ): boolean {
396
+ return (
397
+ uiElementId != null &&
398
+ randomId != null &&
399
+ previewedTables.get(uiElementId) === randomId
400
+ );
401
+ }
402
+
403
+ function markTablePreviewed(
404
+ uiElementId: UIElementId | null | undefined,
405
+ randomId: string | null | undefined,
406
+ ): void {
407
+ if (uiElementId != null && randomId != null) {
408
+ previewedTables.set(uiElementId, randomId);
409
+ }
410
+ }
411
+
380
412
  const LazyDataTableComponent = ({
381
413
  isLazy: initialIsLazy,
382
414
  children,
383
415
  preload,
416
+ host,
384
417
  }: {
385
418
  isLazy: boolean;
386
419
  children: React.ReactNode;
387
420
  preload: boolean;
421
+ host: HTMLElement;
388
422
  }) => {
389
- const [isLazy, setIsLazy] = useState(initialIsLazy && !preload);
423
+ const parentElement = host.closest(`[${OBJECT_ID_ATTR}]`);
424
+ const uiElementId = parentElement ? UIElementId.parse(parentElement) : null;
425
+
426
+ const randomId = host
427
+ .closest(`[${RANDOM_ID_ATTR}]`)
428
+ ?.getAttribute(RANDOM_ID_ATTR);
429
+
430
+ const [isLazy, setIsLazy] = useState(
431
+ initialIsLazy && !preload && !wasTablePreviewed(uiElementId, randomId),
432
+ );
390
433
 
391
434
  if (isLazy) {
392
435
  return (
393
436
  <div className="flex h-20 items-center justify-center">
394
- <Button variant="outline" size="xs" onClick={() => setIsLazy(false)}>
437
+ <Button
438
+ variant="outline"
439
+ size="xs"
440
+ onClick={() => {
441
+ markTablePreviewed(uiElementId, randomId);
442
+ setIsLazy(false);
443
+ }}
444
+ >
395
445
  <Table2Icon className="mr-2 h-4 w-4" />
396
446
  Preview data
397
447
  </Button>
@@ -15,6 +15,7 @@ import { Label } from "@/components/ui/label";
15
15
  import { NativeSelect } from "@/components/ui/native-select";
16
16
  import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
17
17
  import { toast } from "@/components/ui/use-toast";
18
+ import { RANDOM_ID_ATTR } from "@/core/dom/ui-element-constants";
18
19
  import { useAsyncData } from "@/hooks/useAsyncData";
19
20
  import { useInternalStateWithSync } from "@/hooks/useInternalStateWithSync";
20
21
  import { cn } from "@/utils/cn";
@@ -150,7 +151,9 @@ export const FileBrowser = ({
150
151
 
151
152
  // HACK: use the random-id of the host element to force a re-render
152
153
  // when the random-id changes, this means the cell was re-rendered
153
- const randomId = host.closest("[random-id]")?.getAttribute("random-id");
154
+ const randomId = host
155
+ .closest(`[${RANDOM_ID_ATTR}]`)
156
+ ?.getAttribute(RANDOM_ID_ATTR);
154
157
 
155
158
  const { data, error, isPending } = useAsyncData(() => {
156
159
  return list_directory({ path: path });
@@ -4,6 +4,7 @@
4
4
  import type { AnyWidget } from "@anywidget/types";
5
5
  import { useEffect, useRef } from "react";
6
6
  import { z } from "zod";
7
+ import { RANDOM_ID_ATTR } from "@/core/dom/ui-element-constants";
7
8
  import { useAsyncData } from "@/hooks/useAsyncData";
8
9
  import type { HTMLElementNotDerivedFromRef } from "@/hooks/useEventListener";
9
10
  import { createPlugin } from "@/plugins/core/builder";
@@ -145,7 +146,9 @@ const AnyWidgetSlot = (props: IPluginProps<ModelIdRef, Data>) => {
145
146
  }
146
147
 
147
148
  // Find the closest parent element with an attribute of `random-id`
148
- const randomId = props.host.closest("[random-id]")?.getAttribute("random-id");
149
+ const randomId = props.host
150
+ .closest(`[${RANDOM_ID_ATTR}]`)
151
+ ?.getAttribute(RANDOM_ID_ATTR);
149
152
  const key = randomId ?? jsUrl;
150
153
 
151
154
  return (