@marimo-team/islands 0.25.3-dev322 → 0.25.3-dev348

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/dist/{ConnectedDataExplorerComponent-DkXBUBf3.js → ConnectedDataExplorerComponent-xliyyXNO.js} +16 -16
  2. package/dist/{ErrorBoundary-CYa6lyKz.js → ErrorBoundary-CIimOpqs.js} +7 -7
  3. package/dist/{abnfDiagram-VCTEODGH-BPS-pTU2.js → abnfDiagram-VCTEODGH-Dl9Nrtbf.js} +2 -2
  4. package/dist/{any-language-editor-DIS7IdV1.js → any-language-editor-CBHhdCC0.js} +2 -2
  5. package/dist/{architectureDiagram-5GKGNRK7-DB2MHOb7.js → architectureDiagram-5GKGNRK7-DTg032fQ.js} +1 -1
  6. package/dist/{assertNever-HbUt8DyH.js → assertNever-ie77PjMn.js} +13 -13
  7. package/dist/{chat-ui-85k50jaj.js → chat-ui-BV0qyfru.js} +51 -51
  8. package/dist/{check-CApDh1-n.js → check-DTIv5ZZ3.js} +1 -1
  9. package/dist/{chunk-IMKFNOWR-CjFCedjx.js → chunk-IMKFNOWR-CsFB-2LW.js} +1 -1
  10. package/dist/{chunk-SHT3W25Y-Dptltg0_.js → chunk-SHT3W25Y-ByuJHeBL.js} +1 -1
  11. package/dist/{chunk-SVP7TREG-2jBN760m.js → chunk-SVP7TREG-9oBZhbrr.js} +1 -1
  12. package/dist/{chunk-TICWLB2K-CS8BE-b9.js → chunk-TICWLB2K-Bx4m0cJ1.js} +1 -1
  13. package/dist/{classDiagram-ZZMXUADV-HtQWp-Cm.js → classDiagram-ZZMXUADV-Cms37-C9.js} +2 -2
  14. package/dist/{classDiagram-v2-VYDZK3BY-DA9LETN-.js → classDiagram-v2-VYDZK3BY-qAom4Bi7.js} +2 -2
  15. package/dist/{code-visibility-CGcIJMGj.js → code-visibility-_-8-dvb4.js} +20 -20
  16. package/dist/{common-B-Ng3J3K.js → common-CNXuhphD.js} +620 -620
  17. package/dist/{config-B171m9zn.js → config-CvBnR945.js} +3 -3
  18. package/dist/{copy-icon-k2_Fx5Qv.js → copy-icon-DmtNidSa.js} +2 -2
  19. package/dist/{createReducer-BPPI11pP.js → createReducer-BxBCTvKn.js} +5 -5
  20. package/dist/{cynefinDiagram-5FMLGOSQ-eyocrg_E.js → cynefinDiagram-5FMLGOSQ-BjJZwMEz.js} +1 -1
  21. package/dist/{dagre-GXQ25YYZ-Dtm96Nvx.js → dagre-GXQ25YYZ-Bohu7_09.js} +1 -1
  22. package/dist/{data-grid-overlay-editor-CUD6F0Vj.js → data-grid-overlay-editor--4Gk_rwA.js} +1 -1
  23. package/dist/{diagram-S7CK7UJ4-C09TrdNO.js → diagram-S7CK7UJ4-Chl0n835.js} +1 -1
  24. package/dist/{diagram-UQ7AKVKN-vHUsSML6.js → diagram-UQ7AKVKN-DHx8nzy7.js} +1 -1
  25. package/dist/{diagram-VX7I27RA-Ch4mRgz8.js → diagram-VX7I27RA-CKdpv1U4.js} +1 -1
  26. package/dist/{diagram-Z3DM3KII-DyFQNdAL.js → diagram-Z3DM3KII-sY-sI--2.js} +1 -1
  27. package/dist/{dialog-C2Muh059.js → dialog-Cqu5Hp5U.js} +21 -21
  28. package/dist/{dropdown-menu-BGbNnuFq.js → dropdown-menu-DXEYk99S.js} +707 -707
  29. package/dist/{ebnfDiagram-PWID7BFC-N3SvlrCs.js → ebnfDiagram-PWID7BFC-CD0P2xeH.js} +2 -2
  30. package/dist/{erDiagram-RLTQ6QDP-RR2q2xtd.js → erDiagram-RLTQ6QDP-BaKQuYxC.js} +1 -1
  31. package/dist/{error-banner-3_4Lc93Y.js → error-banner-DUB_qUeL.js} +58 -58
  32. package/dist/{error-entries-BhNAZWU1.js → error-entries-CTb_cxXY.js} +310 -310
  33. package/dist/{feedback-button-DK6zUzAk.js → feedback-button-BaA4r2dl.js} +7 -7
  34. package/dist/{flowDiagram-HODETNUW-nxd7WhlW.js → flowDiagram-HODETNUW-BcCpHh7-.js} +2 -2
  35. package/dist/{formats-aUcw6MrS.js → formats-hF6fETr6.js} +1 -1
  36. package/dist/{glide-data-editor-O-94UN5R.js → glide-data-editor-CRn3KXyR.js} +3129 -3119
  37. package/dist/{glide-utils-D4UP8TGv.js → glide-utils-Bnd_k48H.js} +2 -2
  38. package/dist/{globals-BVjYePCn.js → globals-_FmD97Ui.js} +3 -3
  39. package/dist/{html-to-image-DOOtP-4-.js → html-to-image-Bbl0PeC7.js} +478 -478
  40. package/dist/{infoDiagram-27XIBGKW-CUYXQKGf.js → infoDiagram-27XIBGKW-DWltseIa.js} +1 -1
  41. package/dist/{ishikawaDiagram-5VMMS53U-v6gsCrlK.js → ishikawaDiagram-5VMMS53U-DHgepBPo.js} +1 -1
  42. package/dist/{kanban-definition-UXKFOSKX-z236-0Ok.js → kanban-definition-UXKFOSKX-B2_BIjBq.js} +1 -1
  43. package/dist/main.js +6712 -6590
  44. package/dist/{mermaid-C7lQ_2LJ.js → mermaid--2K0cmt9.js} +34 -34
  45. package/dist/{mindmap-definition-YA3MSWOX--pZF7OCY.js → mindmap-definition-YA3MSWOX-DNebycIP.js} +1 -1
  46. package/dist/{number-overlay-editor-jkzzCN4e.js → number-overlay-editor-Bcg25qrp.js} +1 -1
  47. package/dist/{pegDiagram-XKGWAZYB-Bgw0Zz_6.js → pegDiagram-XKGWAZYB-CH4U3II-.js} +2 -2
  48. package/dist/{pencil-RXFY-Ek8.js → pencil-MMnw1YQh.js} +1 -1
  49. package/dist/{pieDiagram-E7YTZNPT-B27tDYvK.js → pieDiagram-E7YTZNPT-pfcrdUCa.js} +1 -1
  50. package/dist/{process-output-CiX_Dl4L.js → process-output-Dw_sI4Xp.js} +4 -4
  51. package/dist/{railroadDiagram-O6MQD6OU-CBjPhGpj.js → railroadDiagram-O6MQD6OU-BZKpAbNG.js} +2 -2
  52. package/dist/{requirementDiagram-BXWQKSXE-DDDrLEw9.js → requirementDiagram-BXWQKSXE-DnyfIHe3.js} +1 -1
  53. package/dist/{reveal-component-CvPmNI5C.js → reveal-component-BVC491am.js} +18 -18
  54. package/dist/{select-Cv6lR39D.js → select-suaAxFuo.js} +286 -286
  55. package/dist/{sequenceDiagram-WJ2MYXX4-DQPaMygQ.js → sequenceDiagram-WJ2MYXX4-BeYuWX7N.js} +1 -1
  56. package/dist/{spec-BoCLWa-M.js → spec-BQNx2-D7.js} +4 -4
  57. package/dist/{stateDiagram-D77RDMKH-BDoDVVnq.js → stateDiagram-D77RDMKH-B8FxwAM2.js} +2 -2
  58. package/dist/{stateDiagram-v2-MP3YSRHH-KNko46uJ.js → stateDiagram-v2-MP3YSRHH-CzWLBqm1.js} +2 -2
  59. package/dist/{static-state-DgdOadIH.js → static-state-Al_hmf1n.js} +6 -6
  60. package/dist/style.css +1 -1
  61. package/dist/{swimlanes-42K2YHIH-boFjOuiI.js → swimlanes-42K2YHIH-CzKmGMts.js} +1 -1
  62. package/dist/{swimlanesDiagram-VR7AAH4N-BC4FLNwY.js → swimlanesDiagram-VR7AAH4N-DnWIUN_9.js} +2 -2
  63. package/dist/{timeline-definition-24CTP7MA-CLVPYA-v.js → timeline-definition-24CTP7MA-Czf2QN2R.js} +1 -1
  64. package/dist/{toast-CUuX7uXg.js → toast-CsJa1nGj.js} +66 -66
  65. package/dist/{tooltip-DBv8QUvS.js → tooltip-BVbJPkcl.js} +49 -49
  66. package/dist/{use-restore-focus-BxJh5Zfa.js → use-restore-focus-tPmARo1Q.js} +1035 -1022
  67. package/dist/{useDateFormatter-2k4mHRBq.js → useDateFormatter-B4ygYuUU.js} +2 -2
  68. package/dist/{useDeepCompareMemoize-Cb2GRQIS.js → useDeepCompareMemoize-Bcu0DkCS.js} +1 -1
  69. package/dist/{useLifecycle-B2fV3c8N.js → useLifecycle-MTdtnR3-.js} +1 -1
  70. package/dist/{vega-component-do3stf0K.js → vega-component-BhBbP4S7.js} +6 -6
  71. package/dist/{vennDiagram-4TSXK5OY-BLq7sNjb.js → vennDiagram-4TSXK5OY-BrHqWB1U.js} +1 -1
  72. package/dist/{wardleyDiagram-VM6X3IG4-oyJEWWtb.js → wardleyDiagram-VM6X3IG4-Cms1K_3v.js} +1 -1
  73. package/dist/{xychartDiagram-S5SC5T6Z-DMjWgQPm.js → xychartDiagram-S5SC5T6Z-_kUiODzS.js} +1 -1
  74. package/package.json +1 -1
  75. package/src/__mocks__/requests.ts +0 -2
  76. package/src/components/chat/__tests__/notebook-context.test.ts +199 -0
  77. package/src/components/chat/chat-panel.tsx +33 -15
  78. package/src/components/chat/notebook-context.ts +80 -0
  79. package/src/components/editor/cell/StopButton.tsx +3 -3
  80. package/src/components/editor/cell/cell-context-menu.tsx +18 -2
  81. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +38 -1
  82. package/src/components/editor/cell/code/cell-editor.tsx +2 -1
  83. package/src/components/editor/cell/code/language-toggle.tsx +6 -4
  84. package/src/components/editor/chrome/wrapper/__tests__/document-sync-status.test.tsx +9 -6
  85. package/src/components/slides/slide-cell-view.tsx +2 -1
  86. package/src/components/ui/__tests__/fullscreen.test.tsx +56 -1
  87. package/src/components/ui/fullscreen.tsx +18 -2
  88. package/src/components/ui/slider.tsx +6 -2
  89. package/src/core/codemirror/copilot/getCodes.ts +1 -1
  90. package/src/core/edit-app.tsx +5 -4
  91. package/src/core/executions/__tests__/input.test.ts +1 -1
  92. package/src/core/executions/__tests__/interrupt.test.ts +200 -0
  93. package/src/core/executions/__tests__/run.test.ts +3 -3
  94. package/src/core/executions/index.ts +1 -0
  95. package/src/core/executions/interrupt.ts +49 -0
  96. package/src/core/islands/bridge.ts +0 -2
  97. package/src/core/network/requests-network.ts +0 -15
  98. package/src/core/network/requests-static.ts +0 -2
  99. package/src/core/network/requests-toasting.tsx +0 -2
  100. package/src/core/network/types.ts +0 -3
  101. package/src/core/notebook/document/__tests__/model.test.ts +4 -4
  102. package/src/core/notebook/document/__tests__/test-host.test.ts +6 -6
  103. package/src/core/notebook/document/__tests__/test-host.ts +42 -46
  104. package/src/core/notebook/document/__tests__/test-notebook.ts +5 -4
  105. package/src/core/notebook/document/sync.ts +12 -5
  106. package/src/core/notebook/replica/__tests__/fixtures.ts +8 -0
  107. package/src/core/notebook/replica/backend.ts +1 -1
  108. package/src/core/saving/__tests__/document.test.tsx +1 -1
  109. package/src/core/transports/__tests__/static.test.ts +1 -1
  110. package/src/core/transports/__tests__/wasm.test.ts +1 -1
  111. package/src/core/transports/static.ts +8 -2
  112. package/src/core/transports/wasm.ts +2 -4
  113. package/src/core/wasm/__tests__/bridge.test.ts +170 -26
  114. package/src/core/wasm/bridge.ts +64 -19
  115. package/src/core/wasm/worker/types.ts +0 -1
  116. package/src/plugins/impl/DataEditorPlugin.tsx +32 -21
  117. package/src/plugins/impl/SliderPlugin.tsx +35 -3
  118. package/src/plugins/impl/__test__/slider-labels.test.tsx +30 -0
  119. package/src/plugins/impl/__tests__/DataEditorPlugin.test.ts +103 -12
  120. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +40 -0
  121. package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +159 -4
  122. package/src/plugins/impl/data-editor/glide-data-editor.tsx +30 -10
  123. package/src/plugins/impl/data-editor/glide-portal.tsx +5 -3
  124. package/src/plugins/layout/AccordionPlugin.tsx +2 -1
  125. package/src/plugins/layout/__tests__/AccordionPlugin.test.tsx +21 -0
@@ -1,6 +1,12 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import React from "react";
4
+ import {
5
+ CompactSelection,
6
+ type GridColumn,
7
+ type GridSelection,
8
+ type Rectangle,
9
+ } from "@glideapps/glide-data-grid";
4
10
  import { act, render, screen, waitFor } from "@testing-library/react";
5
11
  import { beforeEach, describe, expect, it, vi } from "vitest";
6
12
  import { TooltipProvider } from "@/components/ui/tooltip";
@@ -13,8 +19,13 @@ interface MockDataEditorProps {
13
19
  value: { data: unknown; kind?: unknown },
14
20
  ) => void;
15
21
  onRowAppended?: () => void;
16
- getCellContent?: (cell: [number, number]) => Record<string, unknown>;
22
+ getCellContent?: (cell: readonly [number, number]) => Record<string, unknown>;
17
23
  customRenderers?: unknown[];
24
+ columns?: readonly GridColumn[];
25
+ gridSelection?: GridSelection;
26
+ onGridSelectionChange?: (selection: GridSelection) => void;
27
+ onHeaderMenuClick?: (col: number, bounds: Rectangle) => void;
28
+ onColumnResize?: (column: GridColumn, newSize: number) => void;
18
29
  }
19
30
 
20
31
  const captureDataEditorProps = vi.hoisted(() =>
@@ -41,17 +52,21 @@ const capturedDataEditor = {
41
52
 
42
53
  vi.mock("@glideapps/glide-data-grid", async () => {
43
54
  const React = await import("react");
55
+ const { CompactSelection } = await vi.importActual<
56
+ typeof import("@glideapps/glide-data-grid")
57
+ >("@glideapps/glide-data-grid");
44
58
  return {
45
59
  default: React.forwardRef(function MockDataEditor(
46
60
  props: MockDataEditorProps,
47
61
  _ref: React.Ref<HTMLDivElement>,
48
62
  ) {
49
63
  captureDataEditorProps(props);
64
+ if (props.gridSelection?.current) {
65
+ props.getCellContent?.(props.gridSelection.current.cell);
66
+ }
50
67
  return <div data-testid="mock-data-editor" />;
51
68
  }),
52
- CompactSelection: {
53
- empty: () => ({}),
54
- },
69
+ CompactSelection,
55
70
  GridCellKind: {
56
71
  Text: "text",
57
72
  Number: "number",
@@ -132,6 +147,111 @@ describe("GlideDataEditor portal", () => {
132
147
  ]);
133
148
  });
134
149
 
150
+ it("keeps the grid and selection when cell data changes with the same schema", () => {
151
+ const { rerender } = render(<GlideDataEditor {...editorProps} />);
152
+ const grid = screen.getByTestId("mock-data-editor");
153
+ const selection: GridSelection = {
154
+ columns: CompactSelection.empty(),
155
+ rows: CompactSelection.empty(),
156
+ current: {
157
+ cell: [0, 0],
158
+ range: { x: 0, y: 0, width: 1, height: 1 },
159
+ rangeStack: [],
160
+ },
161
+ };
162
+ act(() =>
163
+ captureDataEditorProps.mock.lastCall?.[0].onGridSelectionChange?.(
164
+ selection,
165
+ ),
166
+ );
167
+
168
+ rerender(
169
+ <GlideDataEditor
170
+ {...editorProps}
171
+ data={[{ name: "bob" }]}
172
+ columnFields={new Map(editorProps.columnFields)}
173
+ />,
174
+ );
175
+
176
+ expect(screen.getByTestId("mock-data-editor")).toBe(grid);
177
+ expect(captureDataEditorProps.mock.lastCall?.[0].gridSelection).toEqual(
178
+ selection,
179
+ );
180
+ expect(capturedDataEditor.getCellContent?.([0, 0])).toMatchObject({
181
+ data: "bob",
182
+ });
183
+ });
184
+
185
+ it.each([
186
+ { operation: "remove", names: ["name"] },
187
+ { operation: "rename", names: ["name", "weekday"] },
188
+ { operation: "insert", names: ["name", "day", "extra"] },
189
+ { operation: "reorder", names: ["day", "name"] },
190
+ ])(
191
+ "preserves widths and resets selection, menu, and grid on column $operation",
192
+ ({ names }) => {
193
+ const columnFields = new Map<string, "string">([
194
+ ["name", "string"],
195
+ ["day", "string"],
196
+ ]);
197
+ const props = {
198
+ ...editorProps,
199
+ columnFields,
200
+ data: [{ name: "alice", day: "Friday" }],
201
+ };
202
+ const { rerender } = render(<GlideDataEditor {...props} />);
203
+ const grid = screen.getByTestId("mock-data-editor");
204
+ act(() => {
205
+ captureDataEditorProps.mock.lastCall?.[0].onColumnResize?.(
206
+ { title: "name", width: 100 },
207
+ 240,
208
+ );
209
+ });
210
+ expect(
211
+ captureDataEditorProps.mock.lastCall?.[0].columns?.[0],
212
+ ).toMatchObject({ width: 240 });
213
+ act(() => {
214
+ captureDataEditorProps.mock.lastCall?.[0].onGridSelectionChange?.({
215
+ columns: CompactSelection.fromSingleSelection(1),
216
+ rows: CompactSelection.fromSingleSelection(0),
217
+ current: {
218
+ cell: [1, 0],
219
+ range: { x: 1, y: 0, width: 1, height: 1 },
220
+ rangeStack: [],
221
+ },
222
+ });
223
+ captureDataEditorProps.mock.lastCall?.[0].onHeaderMenuClick?.(1, {
224
+ x: 0,
225
+ y: 0,
226
+ width: 100,
227
+ height: 30,
228
+ });
229
+ });
230
+ expect(screen.getByText("Copy column name")).toBeInTheDocument();
231
+ captureDataEditorProps.mockClear();
232
+
233
+ const nextFields = new Map<string, "string">(
234
+ names.map((name) => [name, "string"]),
235
+ );
236
+ rerender(<GlideDataEditor {...props} columnFields={nextFields} />);
237
+
238
+ expect(screen.getByTestId("mock-data-editor")).not.toBe(grid);
239
+ expect(screen.queryByText("Copy column name")).toBeNull();
240
+ for (const [rendered] of captureDataEditorProps.mock.calls) {
241
+ expect(rendered.gridSelection).toEqual({
242
+ columns: CompactSelection.empty(),
243
+ rows: CompactSelection.empty(),
244
+ });
245
+ expect(rendered.columns?.map((column) => column.title)).toEqual([
246
+ ...nextFields.keys(),
247
+ ]);
248
+ expect(
249
+ rendered.columns?.find((column) => column.title === "name"),
250
+ ).toMatchObject({ width: 240 });
251
+ }
252
+ },
253
+ );
254
+
135
255
  it("creates a restricted dropdown cell and registers only its renderer", () => {
136
256
  render(
137
257
  <TooltipProvider>
@@ -303,6 +423,41 @@ describe("GlideDataEditor portal", () => {
303
423
  fullscreenContainer.remove();
304
424
  });
305
425
 
426
+ it("moves the portal into a fullscreen element inside a shadow root", async () => {
427
+ const host = document.createElement("div");
428
+ host.append(document.createElement("div"));
429
+ const shadowRoot = host.attachShadow({ mode: "open" });
430
+ const target = document.createElement("div");
431
+ shadowRoot.append(target);
432
+ document.body.append(host);
433
+ Object.defineProperty(shadowRoot, "fullscreenElement", {
434
+ configurable: true,
435
+ get: () => target,
436
+ });
437
+
438
+ render(
439
+ <TooltipProvider>
440
+ <GlideDataEditor {...editorProps} />
441
+ </TooltipProvider>,
442
+ );
443
+
444
+ act(() => {
445
+ // The browser retargets document.fullscreenElement to the shadow host.
446
+ fullscreenElement = host;
447
+ document.dispatchEvent(new Event("fullscreenchange"));
448
+ });
449
+
450
+ await waitFor(() => {
451
+ const portal = target.querySelector(
452
+ "[data-testid='glide-data-editor-portal']",
453
+ );
454
+ expect(portal?.parentElement).toBe(target);
455
+ });
456
+ expect(host.children).toHaveLength(1);
457
+
458
+ host.remove();
459
+ });
460
+
306
461
  it("unmounts cleanly while fullscreen is active", async () => {
307
462
  const fullscreenContainer = document.createElement("div");
308
463
  document.body.appendChild(fullscreenContainer);
@@ -72,13 +72,41 @@ interface GlideDataEditorProps {
72
72
  onAddEdits: (edits: Edits["edits"]) => void;
73
73
  }
74
74
 
75
- export const GlideDataEditor = ({
75
+ export const GlideDataEditor = (props: GlideDataEditorProps) => {
76
+ const [columnWidths, setColumnWidths] = useState<Record<string, number>>({});
77
+ const onColumnResize = useCallback((column: GridColumn, newSize: number) => {
78
+ setColumnWidths((prev) => ({
79
+ ...prev,
80
+ [column.title]: newSize,
81
+ }));
82
+ }, []);
83
+
84
+ return (
85
+ // Changing columns can leave the selection pointing to a removed column.
86
+ // Remount the grid to clear that selection before rendering the new schema.
87
+ <GlideDataEditorInner
88
+ key={JSON.stringify([...props.columnFields])}
89
+ {...props}
90
+ columnWidths={columnWidths}
91
+ onColumnResize={onColumnResize}
92
+ />
93
+ );
94
+ };
95
+
96
+ interface GlideDataEditorInnerProps extends GlideDataEditorProps {
97
+ columnWidths: Readonly<Record<string, number>>;
98
+ onColumnResize: (column: GridColumn, newSize: number) => void;
99
+ }
100
+
101
+ const GlideDataEditorInner = ({
76
102
  data,
77
103
  columnFields,
78
104
  columnTypes,
79
105
  editableColumns,
80
106
  onAddEdits,
81
- }: GlideDataEditorProps) => {
107
+ columnWidths,
108
+ onColumnResize,
109
+ }: GlideDataEditorInnerProps) => {
82
110
  const { theme } = useTheme();
83
111
  const dataEditorRef = useRef<DataEditorRef>(null);
84
112
  const portalElementRef = useRef<HTMLDivElement>(null);
@@ -90,7 +118,6 @@ export const GlideDataEditor = ({
90
118
  rows: CompactSelection.empty(),
91
119
  });
92
120
 
93
- const [columnWidths, setColumnWidths] = useState<Record<string, number>>({});
94
121
  const editableColumnSet = useMemo(
95
122
  () => (editableColumns === "all" ? null : new Set(editableColumns)),
96
123
  [editableColumns],
@@ -215,13 +242,6 @@ export const GlideDataEditor = ({
215
242
  [columns, onAddEdits],
216
243
  );
217
244
 
218
- const onColumnResize = useCallback((column: GridColumn, newSize: number) => {
219
- setColumnWidths((prev) => ({
220
- ...prev,
221
- [column.title]: newSize,
222
- }));
223
- }, []);
224
-
225
245
  // Only called when user edits a cell, not deletes
226
246
  const validateCell = useCallback(
227
247
  (cell: Item, newValue: EditableGridCell, _prevValue: GridCell): boolean => {
@@ -2,6 +2,7 @@
2
2
 
3
3
  import React, { useEffect, useState } from "react";
4
4
  import { createPortal } from "react-dom";
5
+ import { getFullScreenElement } from "@/components/ui/fullscreen";
5
6
 
6
7
  /**
7
8
  * Dismiss any open Glide Data Grid overlay by firing Escape on it.
@@ -18,7 +19,7 @@ export function dismissGlideOverlay() {
18
19
  }
19
20
 
20
21
  function getGlidePortalContainer(): Element {
21
- return document.fullscreenElement ?? document.body;
22
+ return getFullScreenElement() ?? document.body;
22
23
  }
23
24
 
24
25
  function useGlidePortalContainer(): Element {
@@ -26,8 +27,9 @@ function useGlidePortalContainer(): Element {
26
27
 
27
28
  useEffect(() => {
28
29
  const handleFullscreenChange = () => {
29
- if (document.fullscreenElement) {
30
- setContainer(document.fullscreenElement);
30
+ const fullScreenElement = getFullScreenElement();
31
+ if (fullScreenElement) {
32
+ setContainer(fullScreenElement);
31
33
  } else {
32
34
  dismissGlideOverlay();
33
35
  setContainer(document.body);
@@ -57,7 +57,8 @@ const AccordionComponent = ({
57
57
  return (
58
58
  <AccordionItem key={index} value={index.toString()}>
59
59
  <AccordionTrigger className="py-2 text-md">
60
- {renderHTML({ html: labels[index] })}
60
+ {/* The host's light DOM can gain a child that has no label. */}
61
+ {renderHTML({ html: labels[index] ?? "" })}
61
62
  </AccordionTrigger>
62
63
  <AccordionContent className="text-md">{child}</AccordionContent>
63
64
  </AccordionItem>
@@ -0,0 +1,21 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { render, screen } from "@testing-library/react";
4
+ import { describe, expect, it } from "vitest";
5
+ import { AccordionPlugin } from "../AccordionPlugin";
6
+
7
+ describe("AccordionPlugin", () => {
8
+ it("renders a child that has no label", () => {
9
+ const plugin = new AccordionPlugin();
10
+ render(
11
+ plugin.render({
12
+ host: document.createElement("div"),
13
+ data: { labels: ["First"], multiple: false, expanded: [] },
14
+ children: [<p key="first">first</p>, <p key="extra">extra</p>],
15
+ }),
16
+ );
17
+
18
+ expect(screen.getByText("First")).toBeInTheDocument();
19
+ expect(screen.getAllByRole("button")).toHaveLength(2);
20
+ });
21
+ });