@marimo-team/islands 0.24.1-dev115 → 0.24.1-dev119

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 (65) hide show
  1. package/dist/{ConnectedDataExplorerComponent-3vyrwCHu.js → ConnectedDataExplorerComponent-qIKbwYin.js} +10 -10
  2. package/dist/{ErrorBoundary-QF0xsxMw.js → ErrorBoundary-CR-ZQN_c.js} +2 -2
  3. package/dist/{any-language-editor-B2e3EkTe.js → any-language-editor-CpTnK43-.js} +3 -3
  4. package/dist/assets/__vite-browser-external-BdKGjRZ4.js +1 -0
  5. package/dist/assets/{worker-C3TCEtlU.js → worker-DlIBfp9S.js} +2 -2
  6. package/dist/{chat-ui-BgGHMO2G.js → chat-ui-DttQ-5Ut.js} +15 -15
  7. package/dist/{check-C_-c2CXl.js → check-CHig47QV.js} +1 -1
  8. package/dist/{common-Cdx64lyR.js → common-B2pVJVWJ.js} +7590 -7548
  9. package/dist/{copy-D4GHy7GA.js → copy-CD7S58CY.js} +1 -1
  10. package/dist/{dist-mymH-A_x.js → dist-3_o3T78I.js} +14 -14
  11. package/dist/{dropdown-menu-Div6gwlw.js → dropdown-menu-DhCfcscX.js} +123 -123
  12. package/dist/{error-banner-DmgOcFS3.js → error-banner-WDhUzJyg.js} +202 -202
  13. package/dist/{esm-CmFEQJjP.js → esm-CesmlDrs.js} +1 -1
  14. package/dist/{extends-CGTKDWHv.js → extends-BPB6_lTK.js} +3 -3
  15. package/dist/{formats-DuRRWVwb.js → formats-CJMVUfb3.js} +1 -1
  16. package/dist/{glide-data-editor-ekJ5GwaZ.js → glide-data-editor-sPZ8fUFn.js} +7 -7
  17. package/dist/{glide-utils-Dwc8Eh8G.js → glide-utils-NShAPPtn.js} +2 -2
  18. package/dist/{html-to-image-u8Ik6EnK.js → html-to-image-BTYnN4ev.js} +3378 -3378
  19. package/dist/{label-DdGS_CZZ.js → label-lwzzBHUW.js} +1 -1
  20. package/dist/main.js +793 -793
  21. package/dist/{mermaid-Bt45Zzl4.js → mermaid-BsHdQeQf.js} +3 -3
  22. package/dist/{process-output-DQiqt-_U.js → process-output-CsALk6tp.js} +3 -3
  23. package/dist/{reveal-component-tQ-rgxcQ.js → reveal-component-CiaqFa0B.js} +8 -8
  24. package/dist/{schemas-BjeOpZLk.js → schemas-DzK6U94N.js} +1379 -1363
  25. package/dist/{select-DotAQ4jp.js → select-3qHpFKF9.js} +286 -286
  26. package/dist/{spec-BesaYkGC.js → spec-CutWdqo3.js} +4 -4
  27. package/dist/style.css +1 -1
  28. package/dist/{toDate-CrAIOASL.js → toDate-D-Rx3ux5.js} +2 -2
  29. package/dist/{tooltip-COXZxyl7.js → tooltip-Dt77sBeT.js} +87 -87
  30. package/dist/{useAsyncData-x0_tWDWI.js → useAsyncData-CH6-CgJF.js} +1 -1
  31. package/dist/{useDateFormatter-B3V__WqX.js → useDateFormatter-Cp9Jdxls.js} +2 -2
  32. package/dist/{useDeepCompareMemoize-KaRHJEEU.js → useDeepCompareMemoize-JC87C_jO.js} +1 -1
  33. package/dist/{useLifecycle-x0OriHo-.js → useLifecycle-B4RGlyf0.js} +2 -2
  34. package/dist/{useTheme-Bm9_FyqO.js → useTheme-CVJ0UOf2.js} +2 -1
  35. package/dist/{vega-component-nD2oP6WN.js → vega-component-D-IXRnlx.js} +7 -7
  36. package/package.json +1 -1
  37. package/src/components/ai/ai-provider-icon.tsx +2 -2
  38. package/src/components/ai/ai-utils.ts +1 -1
  39. package/src/components/app-config/ai-config.tsx +33 -37
  40. package/src/components/app-config/user-config-form.tsx +1 -1
  41. package/src/components/data-table/renderers.tsx +2 -6
  42. package/src/components/editor/Output.tsx +40 -0
  43. package/src/components/editor/__tests__/Output.test.tsx +85 -2
  44. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +832 -0
  45. package/src/components/editor/file-tree/__tests__/file-viewer.test.tsx +27 -0
  46. package/src/components/editor/file-tree/__tests__/requesting-tree.test.ts +130 -0
  47. package/src/components/editor/file-tree/file-browser-row.tsx +82 -0
  48. package/src/components/editor/file-tree/file-explorer.tsx +377 -159
  49. package/src/components/editor/file-tree/file-operations.tsx +1 -1
  50. package/src/components/editor/file-tree/file-search-results.tsx +142 -0
  51. package/src/components/editor/file-tree/file-tree-row.tsx +40 -0
  52. package/src/components/editor/file-tree/file-viewer.tsx +2 -1
  53. package/src/components/editor/file-tree/requesting-tree.tsx +162 -32
  54. package/src/components/editor/file-tree/use-file-search.ts +142 -0
  55. package/src/components/editor/file-tree/use-hover-expand.ts +34 -0
  56. package/src/components/ui/__tests__/fullscreen.test.tsx +88 -0
  57. package/src/components/ui/fullscreen.tsx +43 -1
  58. package/src/components/ui/links.tsx +2 -1
  59. package/src/core/ai/ids/__tests__/ids.test.ts +0 -8
  60. package/src/core/ai/ids/ids.ts +1 -1
  61. package/src/core/config/config-schema.ts +1 -0
  62. package/src/core/network/__tests__/requests-toasting.test.tsx +14 -0
  63. package/src/core/network/requests-toasting.tsx +1 -1
  64. package/src/hooks/useEventListener.ts +1 -1
  65. package/dist/assets/__vite-browser-external-Cf4O9gnt.js +0 -1
@@ -18,10 +18,12 @@ import {
18
18
  ChevronsDownUpIcon,
19
19
  ChevronsUpDownIcon,
20
20
  ExpandIcon,
21
+ ShrinkIcon,
21
22
  } from "lucide-react";
22
23
  import { tooltipHandler } from "@/components/charts/tooltip";
23
24
  import { useExpandedOutput } from "@/core/cells/outputs";
24
25
  import { viewStateAtom } from "@/core/mode";
26
+ import { useEventListener } from "@/hooks/useEventListener";
25
27
  import { useIframeCapabilities } from "@/hooks/useIframeCapabilities";
26
28
  import { useOverflowDetection } from "@/hooks/useOverflowDetection";
27
29
  import { renderHTML } from "@/plugins/core/RenderHTML";
@@ -31,11 +33,13 @@ import { getContainerWidth } from "@/plugins/impl/vega/utils";
31
33
  import { useTheme } from "@/theme/useTheme";
32
34
  import { Events } from "@/utils/events";
33
35
  import { invariant } from "@/utils/invariant";
36
+ import { Logger } from "@/utils/Logger";
34
37
  import { processMimeBundle } from "@/utils/mime-types";
35
38
  import { Objects } from "@/utils/objects";
36
39
  import { LazyVegaEmbed } from "../charts/lazy";
37
40
  import { ChartLoadingState } from "../data-table/charts/components/chart-states";
38
41
  import { Button } from "../ui/button";
42
+ import { useIsFullScreen } from "../ui/fullscreen";
39
43
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
40
44
  import { Tooltip } from "../ui/tooltip";
41
45
  import { CsvViewer } from "./file-tree/renderers";
@@ -408,6 +412,12 @@ const Div = React.forwardRef<
408
412
  >((props, ref) => <div ref={ref} {...props} />);
409
413
  Div.displayName = "Div";
410
414
 
415
+ function exitFullscreen() {
416
+ document.exitFullscreen().catch((error) => {
417
+ Logger.warn("Failed to exit fullscreen", error);
418
+ });
419
+ }
420
+
411
421
  /**
412
422
  * Detects if there is overflow in the output area and adds a button to optionally expand
413
423
  */
@@ -425,6 +435,16 @@ const ExpandableOutput = React.memo(
425
435
  const [isExpanded, setIsExpanded] = useExpandedOutput(cellId);
426
436
  const isOverflowing = useOverflowDetection(containerRef);
427
437
  const { hasFullscreen } = useIframeCapabilities();
438
+ const isFullscreen = useIsFullScreen(containerRef);
439
+
440
+ // Not every host exits fullscreen on Escape by itself. A null target keeps
441
+ // the listener off every output that is not fullscreen.
442
+ useEventListener(isFullscreen ? document : null, "keydown", (event) => {
443
+ if (event.key !== "Escape" || !document.fullscreenElement) {
444
+ return;
445
+ }
446
+ exitFullscreen();
447
+ });
428
448
 
429
449
  return (
430
450
  <>
@@ -488,6 +508,26 @@ const ExpandableOutput = React.memo(
488
508
  isExpanded || forceExpand ? { maxHeight: "none" } : undefined
489
509
  }
490
510
  >
511
+ {/* Fullscreen paints this element only, and the action buttons sit
512
+ outside it. A host does not always draw an exit overlay, so the
513
+ output carries its own exit control. */}
514
+ {isFullscreen && (
515
+ <Tooltip content="Exit fullscreen" side="left">
516
+ <Button
517
+ data-testid="exit-fullscreen-output-button"
518
+ aria-label="Exit fullscreen"
519
+ className="absolute right-2 top-2 z-2 p-1 bg-background/90 border border-border hover:bg-muted print:hidden"
520
+ onClick={exitFullscreen}
521
+ size="xs"
522
+ variant="text"
523
+ >
524
+ <ShrinkIcon
525
+ className="size-4 opacity-60 hover:opacity-80"
526
+ strokeWidth={1.25}
527
+ />
528
+ </Button>
529
+ </Tooltip>
530
+ )}
491
531
  {children}
492
532
  </div>
493
533
  </div>
@@ -1,6 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- import { render, screen } from "@testing-library/react";
3
- import { describe, expect, it } from "vitest";
2
+ import { act, fireEvent, render, screen } from "@testing-library/react";
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
4
  import { cellId } from "@/__tests__/branded";
5
5
  import { TooltipProvider } from "@/components/ui/tooltip";
6
6
  import { OutputArea, OutputRenderer } from "../Output";
@@ -158,3 +158,86 @@ describe("OutputRenderer image and SVG rendering", () => {
158
158
  expect(imgElement).toHaveAttribute("src", base64PngDataUrl);
159
159
  });
160
160
  });
161
+
162
+ describe("OutputArea fullscreen exit control", () => {
163
+ let fullscreenElement: Element | null = null;
164
+ const exitFullscreen = vi.fn(() => Promise.resolve());
165
+
166
+ beforeEach(() => {
167
+ fullscreenElement = null;
168
+ exitFullscreen.mockClear();
169
+ Object.defineProperty(document, "fullscreenElement", {
170
+ configurable: true,
171
+ get: () => fullscreenElement,
172
+ });
173
+ Object.defineProperty(document, "exitFullscreen", {
174
+ configurable: true,
175
+ value: exitFullscreen,
176
+ });
177
+ });
178
+
179
+ // Drop the stubs so that the real document properties come back.
180
+ afterEach(() => {
181
+ Reflect.deleteProperty(document, "fullscreenElement");
182
+ Reflect.deleteProperty(document, "exitFullscreen");
183
+ });
184
+
185
+ const renderOutput = () =>
186
+ render(
187
+ <TooltipProvider>
188
+ <OutputArea
189
+ output={{
190
+ channel: "output",
191
+ data: "Hello World",
192
+ mimetype: "text/plain",
193
+ }}
194
+ cellId={cellId("test")}
195
+ stale={false}
196
+ loading={false}
197
+ allowExpand={true}
198
+ />
199
+ </TooltipProvider>,
200
+ );
201
+
202
+ const enterFullscreen = (container: HTMLElement) => {
203
+ fullscreenElement = container.querySelector('[data-cell-role="output"]');
204
+ act(() => {
205
+ document.dispatchEvent(new Event("fullscreenchange"));
206
+ });
207
+ };
208
+
209
+ it("hides the exit button outside fullscreen", () => {
210
+ renderOutput();
211
+ expect(
212
+ screen.queryByTestId("exit-fullscreen-output-button"),
213
+ ).not.toBeInTheDocument();
214
+ });
215
+
216
+ it("shows the exit button when the output is the fullscreen element", () => {
217
+ const { container } = renderOutput();
218
+ enterFullscreen(container);
219
+ expect(
220
+ screen.getByTestId("exit-fullscreen-output-button"),
221
+ ).toBeInTheDocument();
222
+ });
223
+
224
+ it("exits fullscreen on click", () => {
225
+ const { container } = renderOutput();
226
+ enterFullscreen(container);
227
+ fireEvent.click(screen.getByTestId("exit-fullscreen-output-button"));
228
+ expect(exitFullscreen).toHaveBeenCalledTimes(1);
229
+ });
230
+
231
+ it("exits fullscreen on Escape", () => {
232
+ const { container } = renderOutput();
233
+ enterFullscreen(container);
234
+ fireEvent.keyDown(document, { key: "Escape" });
235
+ expect(exitFullscreen).toHaveBeenCalledTimes(1);
236
+ });
237
+
238
+ it("ignores Escape outside fullscreen", () => {
239
+ renderOutput();
240
+ fireEvent.keyDown(document, { key: "Escape" });
241
+ expect(exitFullscreen).not.toHaveBeenCalled();
242
+ });
243
+ });