@marimo-team/islands 0.24.3-dev10 → 0.24.3-dev13

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.
@@ -3,12 +3,12 @@ import { c as n, g as r, h as i, t as a } from "./button-CKBkqR9J.js";
3
3
  import { t as o } from "./react-DMWcHin-.js";
4
4
  import { t as s } from "./compiler-runtime-CAH9RLw2.js";
5
5
  import { r as c } from "./copy-CD7S58CY.js";
6
- import { Cn as l, F as u, Ft as d, Gt as f, Hn as p, In as m, Pn as h, Qn as g, Un as _, Vt as v, Wt as y, Xt as b, Yn as x, Zn as S, _ as C, a as w, c as T, d as E, er as ee, f as D, g as te, gn as ne, gt as re, h as ie, hn as ae, ht as oe, in as se, l as ce, m as le, mn as ue, n as de, nn as fe, on as pe, p as me, pn as he, pt as ge, q as _e, r as ve, rn as ye, rr as be, rt as xe, t as Se, v as Ce, vn as we, x as Te, y as Ee, yt as De, zt as Oe } from "./html-to-image-BtFMe_Nq.js";
6
+ import { Cn as l, F as u, Ft as d, Gt as f, Hn as p, In as m, Pn as h, Qn as g, Un as _, Vt as v, Wt as y, Xt as b, Yn as x, Zn as S, _ as C, a as w, c as T, d as E, er as ee, f as D, g as te, gn as ne, gt as re, h as ie, hn as ae, ht as oe, in as se, l as ce, m as le, mn as ue, n as de, nn as fe, on as pe, p as me, pn as he, pt as ge, q as _e, r as ve, rn as ye, rr as be, rt as xe, t as Se, v as Ce, vn as we, x as Te, y as Ee, yt as De, zt as Oe } from "./html-to-image-B3oyXfx7.js";
7
7
  import { u as ke } from "./dist-3_o3T78I.js";
8
8
  import { C as Ae, I as je, R as Me, b as Ne } from "./select-3qHpFKF9.js";
9
9
  import { _ as Pe, f as Fe, h as Ie, i as Le, n as Re, t as ze } from "./dropdown-menu-DhCfcscX.js";
10
10
  import { p as Be, v as Ve } from "./toDate-CpXBeDq0.js";
11
- import { d as He, f as Ue, i as We, n as Ge, t as Ke, u as qe } from "./process-output-CPy0YKUe.js";
11
+ import { d as He, f as Ue, i as We, n as Ge, t as Ke, u as qe } from "./process-output-ew3B35of.js";
12
12
  import { t as Je } from "./jsx-runtime-CpoKNRgu.js";
13
13
  import { Cn as O, Cr as Ye, In as k, It as Xe, Jn as A, Sn as Ze, Tt as Qe, Yn as $e, bt as et, cn as tt, jn as nt, kt as j, ln as M, mr as rt, un as N, vt as it, wt as at, xt as ot, zt as st } from "./schemas-DzK6U94N.js";
14
14
  import { n as ct } from "./error-banner-CsXbEEjo.js";
@@ -3,7 +3,7 @@ import { _ as n, c as r, f as i, g as a, h as o, m as s, n as c, p as l, r as u,
3
3
  import { t as f } from "./react-DMWcHin-.js";
4
4
  import { t as p } from "./compiler-runtime-CAH9RLw2.js";
5
5
  import { n as m, r as h, t as g } from "./copy-CD7S58CY.js";
6
- import { $ as _, $n as v, $t as y, A as b, An as x, At as S, Cn as C, Ct as w, D as T, E, Et as D, F as O, Gn as k, H as A, Hn as j, Ht as M, Jn as N, Kt as P, Ln as F, M as I, Mn as L, N as R, Nt as ee, Pn as z, Q as te, Qn as ne, Qt as re, Rt as ie, S as ae, T as oe, Tt as se, Un as ce, Ut as le, V as ue, Wt as de, Xt as fe, Yn as pe, Z as me, Zn as he, _ as ge, _t as _e, ar as ve, cn as ye, en as be, er as xe, g as Se, gt as Ce, ht as we, it as Te, jn as Ee, jt as De, kt as Oe, lt as ke, mt as Ae, n as je, on as Me, or as Ne, ot as Pe, pt as Fe, q as Ie, qn as Le, r as Re, rr as ze, s as Be, sr as Ve, t as He, tt as Ue, u as We, v as Ge, vt as Ke, w as qe, wn as Je, y as Ye, yn as Xe, yt as Ze } from "./html-to-image-BtFMe_Nq.js";
6
+ import { $ as _, $n as v, $t as y, A as b, An as x, At as S, Cn as C, Ct as w, D as T, E, Et as D, F as O, Gn as k, H as A, Hn as j, Ht as M, Jn as N, Kt as P, Ln as F, M as I, Mn as L, N as R, Nt as ee, Pn as z, Q as te, Qn as ne, Qt as re, Rt as ie, S as ae, T as oe, Tt as se, Un as ce, Ut as le, V as ue, Wt as de, Xt as fe, Yn as pe, Z as me, Zn as he, _ as ge, _t as _e, ar as ve, cn as ye, en as be, er as xe, g as Se, gt as Ce, ht as we, it as Te, jn as Ee, jt as De, kt as Oe, lt as ke, mt as Ae, n as je, on as Me, or as Ne, ot as Pe, pt as Fe, q as Ie, qn as Le, r as Re, rr as ze, s as Be, sr as Ve, t as He, tt as Ue, u as We, v as Ge, vt as Ke, w as qe, wn as Je, y as Ye, yn as Xe, yt as Ze } from "./html-to-image-B3oyXfx7.js";
7
7
  import { o as Qe, u as B } from "./dist-3_o3T78I.js";
8
8
  import { c as $e, i as et, r as tt } from "./useLifecycle-B4RGlyf0.js";
9
9
  import { a as nt, i as rt, n as it, o as at, r as ot, t as st } from "./useDateFormatter-Cp9Jdxls.js";
@@ -32164,7 +32164,7 @@ function XK() {
32164
32164
  return a.warn("Failed to get version from mount config"), null;
32165
32165
  }
32166
32166
  }
32167
- var ZK = XK() || "0.24.3-dev10", QK = V(ZK), $K = V(!0);
32167
+ var ZK = XK() || "0.24.3-dev13", QK = V(ZK), $K = V(!0);
32168
32168
  V(null);
32169
32169
  //#endregion
32170
32170
  //#region src/core/wasm/state.ts
@@ -10454,7 +10454,7 @@ function If(e) {
10454
10454
  }
10455
10455
  function Lf() {
10456
10456
  return {
10457
- latestEngineSelected: wf,
10457
+ latestEngineSelected: null,
10458
10458
  connectionsMap: Pf
10459
10459
  };
10460
10460
  }
@@ -10462,7 +10462,7 @@ var { reducer: Rf, createActions: zf, valueAtom: Bf, useActions: Vf } = D(Lf, {
10462
10462
  addDataSourceConnection: (e, t) => {
10463
10463
  if (t.connections.length === 0) return e;
10464
10464
  let { latestEngineSelected: n, connectionsMap: r } = e, i = new Map(r);
10465
- for (let e of t.connections) i.set(e.name, e);
10465
+ for (let e of t.connections) i.delete(e.name), i.set(e.name, e);
10466
10466
  return {
10467
10467
  latestEngineSelected: n,
10468
10468
  connectionsMap: i
@@ -10471,18 +10471,18 @@ var { reducer: Rf, createActions: zf, valueAtom: Bf, useActions: Vf } = D(Lf, {
10471
10471
  filterDataSourcesFromVariables: (e, t) => {
10472
10472
  let { latestEngineSelected: n, connectionsMap: r } = e, i = new Set(t), a = new Map([...r].filter(([e]) => Tf.has(e) ? !0 : i.has(e)));
10473
10473
  return {
10474
- latestEngineSelected: a.has(n) ? n : wf,
10474
+ latestEngineSelected: n && a.has(n) ? n : null,
10475
10475
  connectionsMap: a
10476
10476
  };
10477
10477
  },
10478
10478
  clearDataSourceConnections: () => ({
10479
- latestEngineSelected: wf,
10479
+ latestEngineSelected: null,
10480
10480
  connectionsMap: /* @__PURE__ */ new Map()
10481
10481
  }),
10482
10482
  removeDataSourceConnection: (e, t) => {
10483
10483
  let { latestEngineSelected: n, connectionsMap: r } = e, i = new Map(r);
10484
10484
  return i.delete(t), {
10485
- latestEngineSelected: i.has(n) ? n : wf,
10485
+ latestEngineSelected: n && i.has(n) ? n : null,
10486
10486
  connectionsMap: i
10487
10487
  };
10488
10488
  },
@@ -26251,7 +26251,8 @@ function lx() {
26251
26251
  //#region src/core/codemirror/language/languages/sql/sql.ts
26252
26252
  var ux = vf, dx = "DuckDB", fx = new Ft();
26253
26253
  function px() {
26254
- return F.get(Bf).latestEngineSelected;
26254
+ let { latestEngineSelected: e, connectionsMap: t } = F.get(Bf);
26255
+ return e === null ? [...t.values()].reverse().find((e) => !Tf.has(e.name) && e.source !== "iceberg")?.name ?? "__marimo_duckdb" : e;
26255
26256
  }
26256
26257
  var mx = class {
26257
26258
  parser = new Em();
package/dist/main.js CHANGED
@@ -3,16 +3,16 @@ import { _ as a, c as o, f as s, g as c, h as l, i as u, l as d, m as f, n as p,
3
3
  import { t as y } from "./react-DMWcHin-.js";
4
4
  import { t as b } from "./compiler-runtime-CAH9RLw2.js";
5
5
  import { n as x, r as S, t as C } from "./copy-CD7S58CY.js";
6
- import { $n as w, An as T, B as E, Bn as D, Bt as O, C as k, Ct as A, Dn as j, Dt as M, En as N, F as P, Fn as F, G as I, I as L, It as ee, J as te, Jt as R, K as ne, Kn as z, L as re, Lt as ie, Mt as ae, Nn as oe, Nt as se, On as ce, Ot as le, P as ue, Pn as de, Pt as fe, Q as pe, Qn as me, Qt as he, R as ge, Rn as _e, Sn as ve, St as ye, U as be, Un as xe, Vn as Se, Vt as Ce, W as we, Wn as Te, Wt as Ee, X as De, Xn as Oe, Y as ke, Yn as Ae, Yt as je, Z as Me, Zn as Ne, Zt as Pe, _ as Fe, _n as Ie, a as Le, an as Re, ar as ze, at as Be, b as Ve, bn as He, bt as Ue, cn as We, ct as Ge, dn as Ke, dt as qe, et as Je, fn as Ye, ft as Xe, g as Ze, i as Qe, ir as $e, j as et, jt as tt, k as nt, kn as rt, kt as it, ln as at, n as ot, nr as st, o as ct, on as lt, ot as ut, q as dt, qt as ft, sn as pt, st as mt, tn as ht, tr as gt, un as _t, ut as vt, v as yt, wn as bt, wt as xt, xn as St, xt as Ct, y as wt, z as Tt, zn as Et } from "./html-to-image-BtFMe_Nq.js";
6
+ import { $n as w, An as T, B as E, Bn as D, Bt as O, C as k, Ct as A, Dn as j, Dt as M, En as N, F as P, Fn as F, G as I, I as L, It as ee, J as te, Jt as R, K as ne, Kn as z, L as re, Lt as ie, Mt as ae, Nn as oe, Nt as se, On as ce, Ot as le, P as ue, Pn as de, Pt as fe, Q as pe, Qn as me, Qt as he, R as ge, Rn as _e, Sn as ve, St as ye, U as be, Un as xe, Vn as Se, Vt as Ce, W as we, Wn as Te, Wt as Ee, X as De, Xn as Oe, Y as ke, Yn as Ae, Yt as je, Z as Me, Zn as Ne, Zt as Pe, _ as Fe, _n as Ie, a as Le, an as Re, ar as ze, at as Be, b as Ve, bn as He, bt as Ue, cn as We, ct as Ge, dn as Ke, dt as qe, et as Je, fn as Ye, ft as Xe, g as Ze, i as Qe, ir as $e, j as et, jt as tt, k as nt, kn as rt, kt as it, ln as at, n as ot, nr as st, o as ct, on as lt, ot as ut, q as dt, qt as ft, sn as pt, st as mt, tn as ht, tr as gt, un as _t, ut as vt, v as yt, wn as bt, wt as xt, xn as St, xt as Ct, y as wt, z as Tt, zn as Et } from "./html-to-image-B3oyXfx7.js";
7
7
  import { o as Dt, s as Ot, u as kt } from "./dist-3_o3T78I.js";
8
8
  import { D as At, E as jt, O as Mt, S as Nt, T as Pt, _ as Ft, a as It, c as Lt, d as Rt, f as zt, g as Bt, h as Vt, i as Ht, l as Ut, m as Wt, n as Gt, o as Kt, p as qt, r as Jt, s as Yt, t as Xt, u as Zt, v as Qt, w as $t, x as en, y as tn } from "./spec-CutWdqo3.js";
9
- import { $ as nn, $t as rn, A as an, At as on, B as sn, Bt as cn, C as ln, Cn as un, Ct as dn, D as fn, Dn as pn, Dt as mn, E as hn, En as gn, Et as _n, F as vn, Ft as yn, G as bn, Gt as xn, H as Sn, Ht as Cn, I as wn, It as Tn, J as En, Jt as Dn, K as On, Kt as kn, L as An, Lt as jn, M as Mn, N as Nn, Nt as Pn, O as Fn, Ot as In, P as Ln, Pt as Rn, Q as zn, Qt as Bn, R as Vn, Rt as Hn, S as Un, Sn as Wn, St as Gn, T as Kn, Tn as qn, Tt as Jn, U as Yn, Ut as Xn, V as Zn, Vt as Qn, W as $n, Wt as er, X as tr, Xt as nr, Y as rr, Yt as ir, Z as ar, Zt as or, _ as sr, _n as cr, _t as lr, a as ur, an as dr, at as fr, b as pr, bn as mr, bt as hr, cn as gr, dn as _r, dt as vr, en as yr, et as br, fn as xr, ft as Sr, g as Cr, gn as wr, gt as Tr, hn as Er, ht as Dr, i as Or, in as kr, it as Ar, j as jr, k as Mr, kt as Nr, l as Pr, ln as Fr, m as Ir, mn as Lr, mt as Rr, n as zr, nn as Br, nt as Vr, on as Hr, ot as Ur, pn as Wr, pt as Gr, q as Kr, qt as qr, rn as Jr, rt as Yr, s as Xr, sn as Zr, st as Qr, t as $r, tn as ei, tt as ti, un as ni, v as ri, vt as ii, w as ai, wn as oi, wt as si, x as ci, xn as li, xt as ui, y as di, yn as fi, yt as pi, z as mi, zt as hi } from "./common-SEaoDOh7.js";
9
+ import { $ as nn, $t as rn, A as an, At as on, B as sn, Bt as cn, C as ln, Cn as un, Ct as dn, D as fn, Dn as pn, Dt as mn, E as hn, En as gn, Et as _n, F as vn, Ft as yn, G as bn, Gt as xn, H as Sn, Ht as Cn, I as wn, It as Tn, J as En, Jt as Dn, K as On, Kt as kn, L as An, Lt as jn, M as Mn, N as Nn, Nt as Pn, O as Fn, Ot as In, P as Ln, Pt as Rn, Q as zn, Qt as Bn, R as Vn, Rt as Hn, S as Un, Sn as Wn, St as Gn, T as Kn, Tn as qn, Tt as Jn, U as Yn, Ut as Xn, V as Zn, Vt as Qn, W as $n, Wt as er, X as tr, Xt as nr, Y as rr, Yt as ir, Z as ar, Zt as or, _ as sr, _n as cr, _t as lr, a as ur, an as dr, at as fr, b as pr, bn as mr, bt as hr, cn as gr, dn as _r, dt as vr, en as yr, et as br, fn as xr, ft as Sr, g as Cr, gn as wr, gt as Tr, hn as Er, ht as Dr, i as Or, in as kr, it as Ar, j as jr, k as Mr, kt as Nr, l as Pr, ln as Fr, m as Ir, mn as Lr, mt as Rr, n as zr, nn as Br, nt as Vr, on as Hr, ot as Ur, pn as Wr, pt as Gr, q as Kr, qt as qr, rn as Jr, rt as Yr, s as Xr, sn as Zr, st as Qr, t as $r, tn as ei, tt as ti, un as ni, v as ri, vt as ii, w as ai, wn as oi, wt as si, x as ci, xn as li, xt as ui, y as di, yn as fi, yt as pi, z as mi, zt as hi } from "./common-DM9Wj-cL.js";
10
10
  import { c as gi, i as _i, n as vi, o as yi, t as bi } from "./useLifecycle-B4RGlyf0.js";
11
11
  import { t as xi } from "./check-CHig47QV.js";
12
12
  import { A as Si, C as Ci, D as wi, E as Ti, F as Ei, I as Di, M as Oi, N as ki, P as Ai, R as ji, S as Mi, a as Ni, b as Pi, c as Fi, i as Ii, j as Li, l as Ri, n as zi, o as Bi, r as Vi, s as Hi, t as Ui, w as Wi, x as Gi } from "./select-3qHpFKF9.js";
13
13
  import { B as Ki, C as qi, Ft as Ji, J as Yi, Pt as Xi, S as Zi, Ut as Qi, W as $i, Y as ea, _ as ta, a as na, at as ra, bt as ia, c as aa, f as oa, ft as sa, g as ca, h as la, hn as ua, i as da, m as fa, mn as pa, n as ma, ot as ha, p as ga, r as _a, s as va, t as ya, v as ba, x as xa, z as Sa } from "./dropdown-menu-DhCfcscX.js";
14
14
  import { _ as Ca, c as wa, d as Ta, f as Ea, g as Da, h as Oa, m as ka, u as Aa, v as ja } from "./toDate-CpXBeDq0.js";
15
- import { a as Ma, c as Na, d as Pa, f as Fa, i as Ia, l as La, n as Ra, o as za, r as Ba, s as Va, t as Ha } from "./process-output-CPy0YKUe.js";
15
+ import { a as Ma, c as Na, d as Pa, f as Fa, i as Ia, l as La, n as Ra, o as za, r as Ba, s as Va, t as Ha } from "./process-output-ew3B35of.js";
16
16
  import { a as Ua, c as Wa, d as Ga, i as Ka, l as qa, r as Ja, u as Ya } from "./glide-utils-NShAPPtn.js";
17
17
  import { n as Xa, t as Za } from "./ErrorBoundary-CR-ZQN_c.js";
18
18
  import { t as Qa } from "./react-dom-C94wmq5w.js";
@@ -15392,7 +15392,7 @@ var CM = (e) => {
15392
15392
  output: t
15393
15393
  };
15394
15394
  } };
15395
- } }, kM = q.lazy(() => import("./chat-ui-BSwr409J.js").then((e) => ({ default: e.Chatbot }))), AM = U(B({
15395
+ } }, kM = q.lazy(() => import("./chat-ui-CH7Gg-KH.js").then((e) => ({ default: e.Chatbot }))), AM = U(B({
15396
15396
  id: V(),
15397
15397
  role: Qp([
15398
15398
  "system",
@@ -19391,7 +19391,7 @@ var eI = (e) => {
19391
19391
  //#endregion
19392
19392
  //#region src/components/data-table/column-explorer-panel/column-explorer.tsx
19393
19393
  function tI({ previewColumn: e, fieldTypes: t, totalRows: n, totalColumns: r, tableId: i, table: a }) {
19394
- let [o, s] = (0, q.useState)(""), { locale: c } = Ti(), l = t?.filter(([e]) => !(e === "__select__" || e === "_marimo_row_id" || e.startsWith("__m_column__"))), u = l?.filter(([e]) => _r(o, e)), { totalColumns: d, hiddenColumns: f } = $n(a, r), { visible: p } = On(a), m = a.getState().columnVisibility, h = (0, q.useMemo)(() => bn(a), [a, m]), { rowsAndColumns: g, hiddenSuffix: v } = Sn({
19394
+ let [o, s] = (0, q.useState)(""), { locale: c } = Ti(), l = t?.filter(([e]) => !(e === "__select__" || e === "_marimo_row_id" || e.startsWith("__m_column__"))), u = l?.filter(([e]) => _r(o, e)), { totalColumns: d, hiddenColumns: f } = $n(a, r), { visible: p } = On(a), m = bn(a), { rowsAndColumns: h, hiddenSuffix: g } = Sn({
19395
19395
  numRows: n,
19396
19396
  totalColumns: d,
19397
19397
  locale: c,
@@ -19402,8 +19402,8 @@ function tI({ previewColumn: e, fieldTypes: t, totalRows: n, totalColumns: r, ta
19402
19402
  children: [/* @__PURE__ */ (0, J.jsxs)("div", {
19403
19403
  className: "text-xs font-semibold ml-2 flex items-center gap-1",
19404
19404
  children: [
19405
- g,
19406
- f > 0 && /* @__PURE__ */ (0, J.jsx)("span", { children: v }),
19405
+ h,
19406
+ f > 0 && /* @__PURE__ */ (0, J.jsx)("span", { children: g }),
19407
19407
  /* @__PURE__ */ (0, J.jsx)(Wm, {
19408
19408
  tooltip: "Copy column names",
19409
19409
  value: l?.map(([e]) => e).join(",\n") || "",
@@ -19456,7 +19456,7 @@ function tI({ previewColumn: e, fieldTypes: t, totalRows: n, totalColumns: r, ta
19456
19456
  externalType: r,
19457
19457
  previewColumn: e,
19458
19458
  table: a,
19459
- showOnlyColumnState: h,
19459
+ showOnlyColumnState: m,
19460
19460
  defaultExpanded: o === 0
19461
19461
  }, `${i}-${t}`);
19462
19462
  })]
@@ -27066,7 +27066,8 @@ var FV = q.lazy(() => import("./vega-component-ovBXgp91.js")), IV = class {
27066
27066
  tagName = "marimo-accordion";
27067
27067
  validator = B({
27068
27068
  labels: U(V()),
27069
- multiple: W()
27069
+ multiple: W(),
27070
+ expanded: U(V()).default([])
27070
27071
  });
27071
27072
  render(e) {
27072
27073
  return /* @__PURE__ */ (0, J.jsx)(RV, {
@@ -27075,10 +27076,19 @@ var FV = q.lazy(() => import("./vega-component-ovBXgp91.js")), IV = class {
27075
27076
  });
27076
27077
  }
27077
27078
  }, RV = (e) => {
27078
- let t = (0, Y.c)(8), { labels: n, multiple: r, children: i } = e, a = r ? "multiple" : "single", o;
27079
- if (t[0] !== i || t[1] !== n) {
27079
+ let t = (0, Y.c)(11), { labels: n, multiple: r, expanded: i, children: a } = e, o;
27080
+ t[0] !== i || t[1] !== r ? (o = r ? {
27081
+ type: "multiple",
27082
+ defaultValue: i
27083
+ } : {
27084
+ type: "single",
27085
+ defaultValue: i[0],
27086
+ collapsible: !0
27087
+ }, t[0] = i, t[1] = r, t[2] = o) : o = t[2];
27088
+ let s = o, c;
27089
+ if (t[3] !== a || t[4] !== n) {
27080
27090
  let e;
27081
- t[3] === n ? e = t[4] : (e = (e, t) => /* @__PURE__ */ (0, J.jsxs)(yt, {
27091
+ t[6] === n ? e = t[7] : (e = (e, t) => /* @__PURE__ */ (0, J.jsxs)(yt, {
27082
27092
  value: t.toString(),
27083
27093
  children: [/* @__PURE__ */ (0, J.jsx)(wt, {
27084
27094
  className: "py-2 text-md",
@@ -27087,15 +27097,14 @@ var FV = q.lazy(() => import("./vega-component-ovBXgp91.js")), IV = class {
27087
27097
  className: "text-md",
27088
27098
  children: e
27089
27099
  })]
27090
- }, t), t[3] = n, t[4] = e), o = q.Children.map(i, e), t[0] = i, t[1] = n, t[2] = o;
27091
- } else o = t[2];
27092
- let s;
27093
- return t[5] !== o || t[6] !== a ? (s = /* @__PURE__ */ (0, J.jsx)(Ze, {
27094
- type: a,
27100
+ }, t), t[6] = n, t[7] = e), c = q.Children.map(a, e), t[3] = a, t[4] = n, t[5] = c;
27101
+ } else c = t[5];
27102
+ let l;
27103
+ return t[8] !== s || t[9] !== c ? (l = /* @__PURE__ */ (0, J.jsx)(Ze, {
27104
+ ...s,
27095
27105
  className: "text-muted-foreground",
27096
- collapsible: !0,
27097
- children: o
27098
- }), t[5] = o, t[6] = a, t[7] = s) : s = t[7], s;
27106
+ children: c
27107
+ }), t[8] = s, t[9] = c, t[10] = l) : l = t[10], l;
27099
27108
  }, zV = {
27100
27109
  neutral: "neutral",
27101
27110
  info: "info",
@@ -35311,7 +35320,7 @@ function lJ(e) {
35311
35320
  }
35312
35321
  //#endregion
35313
35322
  //#region src/components/editor/renderers/slides-layout/slides-layout.tsx
35314
- var uJ = q.lazy(() => import("./reveal-component-Cun9aLyZ.js")), dJ = (e) => {
35323
+ var uJ = q.lazy(() => import("./reveal-component-DjEi3rsH.js")), dJ = (e) => {
35315
35324
  let t = (0, Y.c)(33), { layout: n, setLayout: r, cells: i, mode: a } = e, o = Xm(it), s = a === "read" || o, c = Xm(ke) > 1, [l, u] = (0, q.useState)(null), d;
35316
35325
  t[0] !== i || t[1] !== n ? (d = sW(i, n), t[0] = i, t[1] = n, t[2] = d) : d = t[2];
35317
35326
  let { slideCells: f, skippedIds: p, noOutputIds: m, slideTypes: h, startCellIndex: g } = d, _;
@@ -1,4 +1,4 @@
1
- import { O as e, nt as t, rt as n } from "./html-to-image-BtFMe_Nq.js";
1
+ import { O as e, nt as t, rt as n } from "./html-to-image-B3oyXfx7.js";
2
2
  import { u as r } from "./dist-3_o3T78I.js";
3
3
  import { b as i } from "./select-3qHpFKF9.js";
4
4
  import { n as a } from "./semaphore-89jsoHtS.js";
@@ -2,9 +2,9 @@ import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
2
2
  import { c as t, g as n, h as r, m as i, t as a } from "./button-CKBkqR9J.js";
3
3
  import { t as o } from "./react-DMWcHin-.js";
4
4
  import { t as s } from "./compiler-runtime-CAH9RLw2.js";
5
- import { kt as c, st as l } from "./html-to-image-BtFMe_Nq.js";
5
+ import { kt as c, st as l } from "./html-to-image-B3oyXfx7.js";
6
6
  import { u } from "./dist-3_o3T78I.js";
7
- import { _n as d, c as f, ct as p, g as m, h, l as g, lt as _, n as v, o as y, r as b, t as x, u as S, ut as C, vn as w, xn as T } from "./common-SEaoDOh7.js";
7
+ import { _n as d, c as f, ct as p, g as m, h, l as g, lt as _, n as v, o as y, r as b, t as x, u as S, ut as C, vn as w, xn as T } from "./common-DM9Wj-cL.js";
8
8
  import { R as E } from "./select-3qHpFKF9.js";
9
9
  import { Y as D } from "./dropdown-menu-DhCfcscX.js";
10
10
  import { t as O } from "./jsx-runtime-CpoKNRgu.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.24.3-dev10",
3
+ "version": "0.24.3-dev13",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -10,7 +10,7 @@ import {
10
10
  EyeIcon,
11
11
  EyeOffIcon,
12
12
  } from "lucide-react";
13
- import { useMemo, useState } from "react";
13
+ import { useState } from "react";
14
14
  import { useLocale } from "react-aria";
15
15
  import {
16
16
  AddDataframeChart,
@@ -98,11 +98,7 @@ export function ColumnExplorerPanel<TData>({
98
98
  hiddenColumns: hiddenColumnCount,
99
99
  } = getColumnCountForDisplay(table, totalColumns);
100
100
  const { visible: visibleColumnCount } = getUserColumnVisibilityCounts(table);
101
- const columnVisibility = table.getState().columnVisibility;
102
- const showOnlyColumnState = useMemo(
103
- () => getShowOnlyColumnState(table),
104
- [table, columnVisibility],
105
- );
101
+ const showOnlyColumnState = getShowOnlyColumnState(table);
106
102
 
107
103
  const { rowsAndColumns, hiddenSuffix } = prettifyRowColumnCount({
108
104
  numRows: totalRows,
@@ -6,7 +6,7 @@ const Slot = SlotPrimitive.Slot;
6
6
 
7
7
  import { useAtomValue } from "jotai";
8
8
  import { ExternalLinkIcon, TriangleAlertIcon } from "lucide-react";
9
- import React, { type PropsWithChildren, useMemo, useState } from "react";
9
+ import React, { type PropsWithChildren, useState } from "react";
10
10
  import { CopyClipboardIcon } from "@/components/icons/copy-icon";
11
11
  import { useImperativeModal } from "@/components/modal/ImperativeModal";
12
12
  import { Button } from "@/components/ui/button";
@@ -94,8 +94,7 @@ export const FeedbackModal: React.FC<{
94
94
  );
95
95
 
96
96
  const notebook = useAtomValue(notebookAtom);
97
- // biome-ignore lint/correctness/useExhaustiveDependencies: recompute when the notebook changes
98
- const errors = useMemo(() => getCellErrorEntries(store), [notebook]);
97
+ const errors = getCellErrorEntries(store);
99
98
 
100
99
  const cells = notebook.cellIds.inOrderIds.map(
101
100
  (cellId) => notebook.cellData[cellId],
@@ -145,10 +144,9 @@ export const FeedbackModal: React.FC<{
145
144
  return contents;
146
145
  }, [includeCode, notebookSourceAvailable, readCode]);
147
146
 
148
- const { url: githubIssueUrl, omitted } = useMemo(() => {
149
- if (!environment) {
150
- return { url: Constants.bugReportUrl, omitted: [] as string[] };
151
- }
147
+ let githubIssueUrl = Constants.bugReportUrl;
148
+ let omitted: string[] = [];
149
+ if (environment) {
152
150
  const fields: Record<string, string> = {
153
151
  env: formatEnvironmentSection(environment),
154
152
  };
@@ -158,8 +156,11 @@ export const FeedbackModal: React.FC<{
158
156
  if (includeCode && codeRequest.data) {
159
157
  fields["reproduction-code"] = formatCodeSection(codeRequest.data);
160
158
  }
161
- return buildBugReportUrl(Constants.bugReportUrl, fields);
162
- }, [environment, errors, includeErrors, includeCode, codeRequest.data]);
159
+ ({ url: githubIssueUrl, omitted } = buildBugReportUrl(
160
+ Constants.bugReportUrl,
161
+ fields,
162
+ ));
163
+ }
163
164
 
164
165
  const omittedLabels = omitted
165
166
  .map((field) =>
@@ -0,0 +1,65 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { render, screen } from "@testing-library/react";
4
+ import { beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { ReadonlyDiff } from "../readonly-diff";
6
+
7
+ const mocks = vi.hoisted(() => ({
8
+ mergeView: vi.fn(),
9
+ useTheme: vi.fn(),
10
+ }));
11
+
12
+ vi.mock("@codemirror/merge", () => ({
13
+ unifiedMergeView: mocks.mergeView,
14
+ }));
15
+
16
+ vi.mock("@uiw/react-codemirror", () => ({
17
+ default: ({ theme, value }: { theme: string; value: string }) => (
18
+ <div data-testid="code-mirror" data-theme={theme} data-value={value} />
19
+ ),
20
+ }));
21
+
22
+ vi.mock("@/theme/useTheme", () => ({
23
+ useTheme: mocks.useTheme,
24
+ }));
25
+
26
+ describe("ReadonlyDiff", () => {
27
+ beforeEach(() => {
28
+ mocks.mergeView.mockReset();
29
+ mocks.mergeView.mockReturnValue([]);
30
+ mocks.useTheme.mockReturnValue({ theme: "light" });
31
+ });
32
+
33
+ it("updates modified code without rebuilding the diff extensions", () => {
34
+ const { rerender } = render(
35
+ <ReadonlyDiff original="before" modified="first revision" />,
36
+ );
37
+
38
+ expect(screen.getByTestId("code-mirror")).toHaveAttribute(
39
+ "data-value",
40
+ "first revision",
41
+ );
42
+ expect(mocks.mergeView).toHaveBeenCalledTimes(1);
43
+
44
+ rerender(<ReadonlyDiff original="before" modified="second revision" />);
45
+
46
+ expect(screen.getByTestId("code-mirror")).toHaveAttribute(
47
+ "data-value",
48
+ "second revision",
49
+ );
50
+ expect(mocks.mergeView).toHaveBeenCalledTimes(1);
51
+ });
52
+
53
+ it("rebuilds diff extensions when the original code changes", () => {
54
+ const { rerender } = render(
55
+ <ReadonlyDiff original="before" modified="after" />,
56
+ );
57
+
58
+ rerender(<ReadonlyDiff original="earlier" modified="after" />);
59
+
60
+ expect(mocks.mergeView).toHaveBeenCalledTimes(2);
61
+ expect(mocks.mergeView).toHaveBeenLastCalledWith(
62
+ expect.objectContaining({ original: "earlier" }),
63
+ );
64
+ });
65
+ });
@@ -21,7 +21,7 @@ export const ReadonlyDiff = memo(
21
21
  },
22
22
  }),
23
23
  ];
24
- }, [props.original, props.modified, theme]);
24
+ }, [props.original]);
25
25
 
26
26
  return (
27
27
  <CodeMirror
@@ -0,0 +1,54 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { act, fireEvent, render, screen } from "@testing-library/react";
4
+ import type React from "react";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+ import { RefreshIconButton } from "../tree-actions";
7
+
8
+ vi.mock("@/components/ui/tooltip", () => ({
9
+ Tooltip: ({ children }: { children: React.ReactNode }) => children,
10
+ }));
11
+
12
+ describe("RefreshIconButton", () => {
13
+ beforeEach(() => {
14
+ vi.useFakeTimers();
15
+ });
16
+
17
+ afterEach(() => {
18
+ vi.useRealTimers();
19
+ });
20
+
21
+ it("spins for at least 500ms after a refresh completes", async () => {
22
+ const onClick = vi.fn().mockResolvedValue(undefined);
23
+ const { container } = render(<RefreshIconButton onClick={onClick} />);
24
+
25
+ fireEvent.click(screen.getByRole("button"));
26
+
27
+ expect(onClick).toHaveBeenCalledOnce();
28
+ expect(container.querySelector("svg")).toHaveClass("animate-[spin_0.5s]");
29
+
30
+ await act(async () => {
31
+ await vi.advanceTimersByTimeAsync(500);
32
+ });
33
+
34
+ expect(container.querySelector("svg")).not.toHaveClass(
35
+ "animate-[spin_0.5s]",
36
+ );
37
+ });
38
+
39
+ it("uses the latest refresh callback after a rerender", async () => {
40
+ const initialOnClick = vi.fn().mockResolvedValue(undefined);
41
+ const updatedOnClick = vi.fn().mockResolvedValue(undefined);
42
+ const { rerender } = render(<RefreshIconButton onClick={initialOnClick} />);
43
+
44
+ rerender(<RefreshIconButton onClick={updatedOnClick} />);
45
+ fireEvent.click(screen.getByRole("button"));
46
+
47
+ expect(initialOnClick).not.toHaveBeenCalled();
48
+ expect(updatedOnClick).toHaveBeenCalledOnce();
49
+
50
+ await act(async () => {
51
+ await vi.advanceTimersByTimeAsync(500);
52
+ });
53
+ });
54
+ });
@@ -7,7 +7,7 @@ import {
7
7
  MoreVerticalIcon,
8
8
  RefreshCwIcon,
9
9
  } from "lucide-react";
10
- import React, { useCallback, useState } from "react";
10
+ import React, { useState } from "react";
11
11
  import { Button, type ButtonProps } from "@/components/ui/button";
12
12
  import { Tooltip } from "@/components/ui/tooltip";
13
13
  import { cn } from "@/utils/cn";
@@ -41,19 +41,16 @@ export const RefreshIconButton: React.FC<{
41
41
  }> = ({ onClick, tooltip = "Refresh", className, iconClassName }) => {
42
42
  const [isSpinning, setIsSpinning] = useState(false);
43
43
 
44
- const handleClick = useCallback(
45
- async (e: React.MouseEvent) => {
46
- setIsSpinning(true);
47
- // Artificially spin for 500ms to show the user that the button is working.
48
- const minDelay = new Promise<void>((r) => setTimeout(r, 500));
49
- try {
50
- await Promise.all([onClick(e), minDelay]);
51
- } finally {
52
- setIsSpinning(false);
53
- }
54
- },
55
- [onClick],
56
- );
44
+ const handleClick = async (e: React.MouseEvent) => {
45
+ setIsSpinning(true);
46
+ // Artificially spin for 500ms to show the user that the button is working.
47
+ const minDelay = new Promise<void>((r) => setTimeout(r, 500));
48
+ try {
49
+ await Promise.all([onClick(e), minDelay]);
50
+ } finally {
51
+ setIsSpinning(false);
52
+ }
53
+ };
57
54
 
58
55
  const button = (
59
56
  <Button
@@ -1,7 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { useAtomValue, useStore } from "jotai";
4
- import { useMemo } from "react";
5
4
  import {
6
5
  useDeleteCellCallback,
7
6
  useDeleteManyCellsCallback,
@@ -130,21 +129,18 @@ export function usePendingDeleteService() {
130
129
  const { entries } = useAtomValue(pendingDeleteStateAtom);
131
130
  const deleteCell = useDeleteCellCallback();
132
131
  const deleteManyCells = useDeleteManyCellsCallback();
133
- return useMemo(
134
- () => ({
135
- submit: (cellIds: CellId[]) => {
136
- submit({ cellIds, deleteCell, deleteManyCells, store });
137
- },
138
- clear,
139
- get idle() {
140
- return entries.size === 0;
141
- },
142
- get shouldConfirmDelete() {
143
- return entries.size > 1;
144
- },
145
- }),
146
- [store, submit, clear, entries, deleteCell, deleteManyCells],
147
- );
132
+ return {
133
+ submit: (cellIds: CellId[]) => {
134
+ submit({ cellIds, deleteCell, deleteManyCells, store });
135
+ },
136
+ clear,
137
+ get idle() {
138
+ return entries.size === 0;
139
+ },
140
+ get shouldConfirmDelete() {
141
+ return entries.size > 1;
142
+ },
143
+ };
148
144
  }
149
145
 
150
146
  export function usePendingDelete(cellId: CellId) {
@@ -17,6 +17,7 @@ import type {
17
17
  import type { DataSourceConnection } from "@/core/datasets/data-source-connections";
18
18
  import {
19
19
  dataSourceConnectionsAtom,
20
+ exportedForTesting as dataSourceTesting,
20
21
  setLatestEngineSelected,
21
22
  } from "@/core/datasets/data-source-connections";
22
23
  import { type ConnectionName, DUCKDB_ENGINE } from "@/core/datasets/engines";
@@ -550,8 +551,8 @@ _df = mo.sql(
550
551
  const getLatestEngine = () =>
551
552
  store.get(dataSourceConnectionsAtom).latestEngineSelected;
552
553
 
553
- it("should use default engine initially", () => {
554
- expect(getLatestEngine()).toBe(DUCKDB_ENGINE);
554
+ it("should have no selected engine initially", () => {
555
+ expect(getLatestEngine()).toBeNull();
555
556
  });
556
557
 
557
558
  it("should persist the selected engine", () => {
@@ -622,6 +623,73 @@ _df = mo.sql(
622
623
  });
623
624
 
624
625
  describe("defaultCode", () => {
626
+ it("should prefer the newest usable connection until an engine is selected", () => {
627
+ const previousState = store.get(dataSourceConnectionsAtom);
628
+ const connection: DataSourceConnection = {
629
+ name: "first_connection" as ConnectionName,
630
+ source: "sqlite",
631
+ display_name: "SQLite",
632
+ dialect: "sqlite",
633
+ databases: [],
634
+ };
635
+ const newestConnection = {
636
+ ...connection,
637
+ name: "newest_connection" as ConnectionName,
638
+ };
639
+ const icebergConnection = {
640
+ ...connection,
641
+ name: "catalog" as ConnectionName,
642
+ source: "iceberg",
643
+ };
644
+ try {
645
+ store.set(dataSourceConnectionsAtom, {
646
+ latestEngineSelected: null,
647
+ connectionsMap: new Map(),
648
+ });
649
+ expect(adapter.defaultMetadata.engine).toBe(DUCKDB_ENGINE);
650
+ expect(adapter.defaultCode).toBe('_df = mo.sql(f"""SELECT * FROM """)');
651
+
652
+ store.set(dataSourceConnectionsAtom, {
653
+ latestEngineSelected: null,
654
+ connectionsMap: new Map([
655
+ [connection.name, connection],
656
+ [newestConnection.name, newestConnection],
657
+ [icebergConnection.name, icebergConnection],
658
+ [DUCKDB_ENGINE, { ...connection, name: DUCKDB_ENGINE }],
659
+ ]),
660
+ });
661
+ expect(adapter.defaultMetadata.engine).toBe(newestConnection.name);
662
+ expect(adapter.defaultCode).toContain("engine=newest_connection");
663
+ expect(
664
+ store.get(dataSourceConnectionsAtom).latestEngineSelected,
665
+ ).toBeNull();
666
+
667
+ const refreshConnection = (connection: DataSourceConnection) => {
668
+ store.set(
669
+ dataSourceConnectionsAtom,
670
+ dataSourceTesting.reducer(store.get(dataSourceConnectionsAtom), {
671
+ type: "addDataSourceConnection",
672
+ payload: { connections: [connection] },
673
+ }),
674
+ );
675
+ };
676
+ refreshConnection(connection);
677
+ expect(adapter.defaultMetadata.engine).toBe(connection.name);
678
+ expect(adapter.defaultCode).toContain("engine=first_connection");
679
+
680
+ setLatestEngineSelected(connection.name);
681
+ refreshConnection(newestConnection);
682
+ expect(adapter.defaultCode).toContain("engine=first_connection");
683
+
684
+ setLatestEngineSelected(DUCKDB_ENGINE);
685
+ refreshConnection(connection);
686
+ expect(adapter.defaultMetadata.engine).toBe(DUCKDB_ENGINE);
687
+ expect(adapter.defaultCode).toBe('_df = mo.sql(f"""SELECT * FROM """)');
688
+ } finally {
689
+ store.set(dataSourceConnectionsAtom, previousState);
690
+ }
691
+ });
692
+
625
693
  it("should include engine in defaultCode when selected", () => {
626
694
  const engine = "postgres_engine" as ConnectionName;
627
695
  setLatestEngineSelected(engine);
@@ -76,7 +76,22 @@ export interface SQLLanguageAdapterMetadata extends SQLMetadata {
76
76
  }
77
77
 
78
78
  function getLatestEngine(): ConnectionName {
79
- return store.get(dataSourceConnectionsAtom).latestEngineSelected;
79
+ const { latestEngineSelected, connectionsMap } = store.get(
80
+ dataSourceConnectionsAtom,
81
+ );
82
+ if (latestEngineSelected !== null) {
83
+ return latestEngineSelected;
84
+ }
85
+
86
+ // Map insertion order prefers the most recently discovered connection.
87
+ const connections = [...connectionsMap.values()].reverse();
88
+ return (
89
+ connections.find(
90
+ (connection) =>
91
+ !INTERNAL_SQL_ENGINES.has(connection.name) &&
92
+ connection.source !== "iceberg",
93
+ )?.name ?? DUCKDB_ENGINE
94
+ );
80
95
  }
81
96
 
82
97
  /**
@@ -106,6 +106,7 @@ describe("data source connections", () => {
106
106
 
107
107
  let newState = addConnection(connections, state);
108
108
  expect(newState.connectionsMap.size).toBe(defaultConnSize + 2);
109
+ newState = { ...newState, latestEngineSelected: connections[0].name };
109
110
 
110
111
  newState = reducer(newState, {
111
112
  type: "removeDataSourceConnection",
@@ -113,6 +114,7 @@ describe("data source connections", () => {
113
114
  });
114
115
  expect(newState.connectionsMap.size).toBe(defaultConnSize + 1);
115
116
  expect(newState.connectionsMap.has("conn2" as ConnectionName)).toBe(true);
117
+ expect(newState.latestEngineSelected).toBeNull();
116
118
  });
117
119
 
118
120
  it("can clear all connections", () => {
@@ -44,7 +44,8 @@ export interface DataSourceConnection extends Omit<
44
44
  export type ConnectionsMap = ReadonlyMap<ConnectionName, DataSourceConnection>;
45
45
 
46
46
  export interface DataSourceState {
47
- latestEngineSelected: ConnectionName;
47
+ // Null permits automatic selection; DuckDB can also be an explicit choice.
48
+ latestEngineSelected: ConnectionName | null;
48
49
  connectionsMap: ConnectionsMap;
49
50
  }
50
51
 
@@ -110,7 +111,7 @@ function tableSchemaPath(sqlTableContext: SQLTableContext): string[] {
110
111
 
111
112
  function initialState(): DataSourceState {
112
113
  return {
113
- latestEngineSelected: DUCKDB_ENGINE,
114
+ latestEngineSelected: null,
114
115
  connectionsMap: initialConnections,
115
116
  };
116
117
  }
@@ -136,6 +137,8 @@ const {
136
137
  // Backend will dedupe by connection name & keep the latest, so we use this as the key
137
138
  const newMap = new Map(connectionsMap);
138
139
  for (const conn of opts.connections) {
140
+ // Refresh insertion order so automatic selection follows discovery recency.
141
+ newMap.delete(conn.name);
139
142
  newMap.set(conn.name, conn);
140
143
  }
141
144
 
@@ -161,16 +164,17 @@ const {
161
164
  }),
162
165
  );
163
166
  return {
164
- // If the latest engine selected is not in the new map, use the default engine
165
- latestEngineSelected: newMap.has(latestEngineSelected)
166
- ? latestEngineSelected
167
- : DUCKDB_ENGINE,
167
+ // Allow automatic selection again when the selected connection disappears.
168
+ latestEngineSelected:
169
+ latestEngineSelected && newMap.has(latestEngineSelected)
170
+ ? latestEngineSelected
171
+ : null,
168
172
  connectionsMap: newMap,
169
173
  };
170
174
  },
171
175
 
172
176
  clearDataSourceConnections: (): DataSourceState => ({
173
- latestEngineSelected: DUCKDB_ENGINE,
177
+ latestEngineSelected: null,
174
178
  connectionsMap: new Map(),
175
179
  }),
176
180
 
@@ -183,9 +187,10 @@ const {
183
187
  const newMap = new Map(connectionsMap);
184
188
  newMap.delete(connectionName);
185
189
  return {
186
- latestEngineSelected: newMap.has(latestEngineSelected)
187
- ? latestEngineSelected
188
- : DUCKDB_ENGINE,
190
+ latestEngineSelected:
191
+ latestEngineSelected && newMap.has(latestEngineSelected)
192
+ ? latestEngineSelected
193
+ : null,
189
194
  connectionsMap: newMap,
190
195
  };
191
196
  },
@@ -23,6 +23,7 @@ interface Data {
23
23
  * Whether to allow multiple tabs to be open.
24
24
  */
25
25
  multiple: boolean;
26
+ expanded: string[];
26
27
  }
27
28
 
28
29
  export class AccordionPlugin implements IStatelessPlugin<Data> {
@@ -31,6 +32,7 @@ export class AccordionPlugin implements IStatelessPlugin<Data> {
31
32
  validator = z.object({
32
33
  labels: z.array(z.string()),
33
34
  multiple: z.boolean(),
35
+ expanded: z.array(z.string()).default([]),
34
36
  });
35
37
 
36
38
  render(props: IStatelessPluginProps<Data>): JSX.Element {
@@ -43,11 +45,14 @@ export class AccordionPlugin implements IStatelessPlugin<Data> {
43
45
  const AccordionComponent = ({
44
46
  labels,
45
47
  multiple,
48
+ expanded,
46
49
  children,
47
50
  }: PropsWithChildren<Data>): JSX.Element => {
48
- const type = multiple ? "multiple" : "single";
51
+ const expansionProps = multiple
52
+ ? { type: "multiple" as const, defaultValue: expanded }
53
+ : { type: "single" as const, defaultValue: expanded[0], collapsible: true };
49
54
  return (
50
- <Accordion type={type} className="text-muted-foreground" collapsible={true}>
55
+ <Accordion {...expansionProps} className="text-muted-foreground">
51
56
  {React.Children.map(children, (child, index) => {
52
57
  return (
53
58
  <AccordionItem key={index} value={index.toString()}>
@@ -0,0 +1,66 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { cleanup, fireEvent, render } from "@testing-library/react";
3
+ import { afterEach, describe, expect, it } from "vitest";
4
+ import { AccordionPlugin } from "../AccordionPlugin";
5
+
6
+ const plugin = new AccordionPlugin();
7
+
8
+ function renderAccordion(multiple: boolean, expanded?: string[]) {
9
+ return plugin.render({
10
+ host: document.createElement("marimo-accordion"),
11
+ data: plugin.validator.parse({
12
+ labels: ["Summary", "Details"],
13
+ multiple,
14
+ expanded,
15
+ }),
16
+ children: [
17
+ <p key="summary">Overview</p>,
18
+ <p key="details">More information</p>,
19
+ ],
20
+ });
21
+ }
22
+
23
+ describe("AccordionPlugin", () => {
24
+ afterEach(cleanup);
25
+
26
+ it.each([
27
+ { multiple: false, expanded: undefined, expected: ["false", "false"] },
28
+ { multiple: true, expanded: [], expected: ["false", "false"] },
29
+ { multiple: false, expanded: ["0"], expected: ["true", "false"] },
30
+ { multiple: false, expanded: ["1"], expected: ["false", "true"] },
31
+ { multiple: true, expanded: ["0", "1"], expected: ["true", "true"] },
32
+ ])(
33
+ "sets initial expansion: $multiple, $expanded",
34
+ ({ multiple, expanded, expected }) => {
35
+ const { getAllByRole } = render(renderAccordion(multiple, expanded));
36
+ expect(
37
+ getAllByRole("button").map((button) =>
38
+ button.getAttribute("aria-expanded"),
39
+ ),
40
+ ).toEqual(expected);
41
+ },
42
+ );
43
+
44
+ it("lets users collapse and switch the initially expanded item", () => {
45
+ const { getAllByRole, rerender } = render(renderAccordion(false, ["1"]));
46
+ const [summary, details] = getAllByRole("button");
47
+ fireEvent.click(details);
48
+ expect(details.getAttribute("aria-expanded")).toBe("false");
49
+ rerender(renderAccordion(false, ["1"]));
50
+ expect(details.getAttribute("aria-expanded")).toBe("false");
51
+ fireEvent.click(details);
52
+ fireEvent.click(summary);
53
+ expect(
54
+ [summary, details].map((button) => button.getAttribute("aria-expanded")),
55
+ ).toEqual(["true", "false"]);
56
+ });
57
+
58
+ it("lets users collapse items independently with multiple enabled", () => {
59
+ const { getAllByRole } = render(renderAccordion(true, ["0", "1"]));
60
+ const [summary, details] = getAllByRole("button");
61
+ fireEvent.click(summary);
62
+ expect(
63
+ [summary, details].map((button) => button.getAttribute("aria-expanded")),
64
+ ).toEqual(["false", "true"]);
65
+ });
66
+ });