@marimo-team/islands 0.25.2-dev4 → 0.25.2-dev7
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/{ConnectedDataExplorerComponent-C2d9ysUI.js → ConnectedDataExplorerComponent-CqhQmp74.js} +11 -11
- package/dist/{ErrorBoundary-CHS5luyo.js → ErrorBoundary-BBXTdyzo.js} +8 -8
- package/dist/{abnfDiagram-VCTEODGH-AFhOQTt8.js → abnfDiagram-VCTEODGH-aLGD44M7.js} +2 -2
- package/dist/{any-language-editor-CycIVIeP.js → any-language-editor-dqxlUDlT.js} +3 -3
- package/dist/{architectureDiagram-5GKGNRK7-BngksAAQ.js → architectureDiagram-5GKGNRK7-BGL5P81u.js} +1 -1
- package/dist/{button-DdXAm8iz.js → button-B0Xgl-Qn.js} +12 -0
- package/dist/{capabilities-B3ce_GNu.js → capabilities-D7Ad8-zw.js} +1 -1
- package/dist/{cells-Cv93oav_.js → cells-5ppES-Un.js} +12 -12
- package/dist/{chat-ui-fVyVOhYN.js → chat-ui-Dv2V95k6.js} +15 -15
- package/dist/{check-B7cIK6gb.js → check-DGZ74tV2.js} +1 -1
- package/dist/{chunk-IMKFNOWR-UOVZuZVn.js → chunk-IMKFNOWR-Cm3RgLDj.js} +1 -1
- package/dist/{chunk-SHT3W25Y-CrEPyWy1.js → chunk-SHT3W25Y-DedDYSP5.js} +1 -1
- package/dist/{chunk-SVP7TREG-uIyyySp6.js → chunk-SVP7TREG-B2SFkCAL.js} +1 -1
- package/dist/{chunk-TICWLB2K-DdXM3eg6.js → chunk-TICWLB2K-DB6hjNOL.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-BiATbBE4.js → classDiagram-ZZMXUADV-BwYhrSEX.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-JnH97GYw.js → classDiagram-v2-VYDZK3BY-HecM04TM.js} +2 -2
- package/dist/{code-visibility-DOzR4Dbu.js → code-visibility-XV7kHdNe.js} +6 -6
- package/dist/{common-BH1qMx9b.js → common-9gwzy4VY.js} +22 -22
- package/dist/{copy-icon-CLdENY__.js → copy-icon-qmRg_VTX.js} +3 -3
- package/dist/{cynefinDiagram-5FMLGOSQ-zknaAsGy.js → cynefinDiagram-5FMLGOSQ-DzaZEqgD.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-BhMOoMGJ.js → dagre-GXQ25YYZ-Cjf5GjU3.js} +1 -1
- package/dist/{data-grid-overlay-editor-C23bOAVI.js → data-grid-overlay-editor-DWmDWNRs.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-V5-Z-EDY.js → diagram-S7CK7UJ4-IhLt_khx.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-qw8AjDal.js → diagram-UQ7AKVKN-CQc6KLPB.js} +1 -1
- package/dist/{diagram-VX7I27RA-DfuqFb1E.js → diagram-VX7I27RA-DHmW9N1T.js} +1 -1
- package/dist/{diagram-Z3DM3KII-DpWwlYZB.js → diagram-Z3DM3KII-zVKsg-Ty.js} +1 -1
- package/dist/{dialog-CgNq6y9-.js → dialog-CILKhbsG.js} +3 -3
- package/dist/{dropdown-menu-CZy06LAq.js → dropdown-menu-BaqM0t4N.js} +7 -7
- package/dist/{ebnfDiagram-PWID7BFC-C20OnNMZ.js → ebnfDiagram-PWID7BFC-DDd5hCRC.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-DBXLWO9v.js → erDiagram-RLTQ6QDP-D10NP0eI.js} +1 -1
- package/dist/{error-banner-B94dcDmP.js → error-banner-DzAmP0AQ.js} +2 -2
- package/dist/{error-entries-DLl0XqoW.js → error-entries-B2Ux3Z2X.js} +2 -2
- package/dist/{feedback-button-B6DzYbxQ.js → feedback-button-BxwB2lCO.js} +10 -10
- package/dist/{flowDiagram-HODETNUW-DL52nMkt.js → flowDiagram-HODETNUW-DgcjSdPV.js} +2 -2
- package/dist/{formats-Cl_67ghI.js → formats-Q9gwlLyD.js} +3 -3
- package/dist/{glide-data-editor-H0BN7EiD.js → glide-data-editor-CY3_ugi6.js} +11 -11
- package/dist/{glide-utils-B3rUT5PI.js → glide-utils-CzzWU59p.js} +3 -3
- package/dist/{globals-C9VJ-IPA.js → globals-N41idH9p.js} +3 -3
- package/dist/{html-to-image-CmkwfNkf.js → html-to-image-BZBwkkDG.js} +11 -11
- package/dist/{infoDiagram-27XIBGKW-ntpxMfrq.js → infoDiagram-27XIBGKW-DrYu3UZC.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-BllIlHE5.js → ishikawaDiagram-5VMMS53U-pmkGQJsA.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-GYtNMjtS.js → kanban-definition-UXKFOSKX-wWGD9VLW.js} +1 -1
- package/dist/{label-BslhXqL6.js → label-MNhNNoN6.js} +2 -2
- package/dist/{loader-DEZSzvma.js → loader-C9OUkMrj.js} +1 -1
- package/dist/main.js +65 -45
- package/dist/{mermaid-Dnzoxjt1.js → mermaid-Dfvntf3s.js} +36 -36
- package/dist/{mindmap-definition-YA3MSWOX-Cth76xgH.js → mindmap-definition-YA3MSWOX-C2f1VIBR.js} +1 -1
- package/dist/{number-overlay-editor-MgsEqPLj.js → number-overlay-editor-BPWS_Cwq.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-Cu4uOXii.js → pegDiagram-XKGWAZYB-D6v5dz4M.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-Bh3eDa9s.js → pieDiagram-E7YTZNPT-BwFAhpYD.js} +1 -1
- package/dist/{process-output-CYN5E-iO.js → process-output-BSsWzfnY.js} +4 -4
- package/dist/{railroadDiagram-O6MQD6OU-DG-M6Rnc.js → railroadDiagram-O6MQD6OU-D6eOFr6Z.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-DCwsThh0.js → requirementDiagram-BXWQKSXE-CHYsMgDR.js} +1 -1
- package/dist/{reveal-component-BNJLyBZU.js → reveal-component-DJQoMaDE.js} +10 -10
- package/dist/{select-C6plhmZr.js → select-BcObMjGz.js} +4 -4
- package/dist/{sequenceDiagram-WJ2MYXX4-CbXB1_PU.js → sequenceDiagram-WJ2MYXX4-BPAknhBp.js} +1 -1
- package/dist/{spec-DQBRQSba.js → spec-D62vWpMT.js} +4 -4
- package/dist/{stateDiagram-D77RDMKH-A-Zf3Sb2.js → stateDiagram-D77RDMKH-Cq249IQ4.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-D9YMnUGx.js → stateDiagram-v2-MP3YSRHH-Dk5QgdXF.js} +2 -2
- package/dist/{swimlanes-42K2YHIH-B_pMf0J4.js → swimlanes-42K2YHIH-Bh_S_HKD.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-Ctgy-yGP.js → swimlanesDiagram-VR7AAH4N-CbztusY8.js} +2 -2
- package/dist/{swiper-component-IOUowglM.js → swiper-component-DkV7T44B.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-DTXFjTTM.js → timeline-definition-24CTP7MA-ByNMgBE7.js} +1 -1
- package/dist/{toDate-C67bxMWn.js → toDate-DWVp6uE1.js} +3 -3
- package/dist/{toast-DvY70eFX.js → toast-DcoJGS3-.js} +4 -4
- package/dist/{tooltip-qB8NlSqV.js → tooltip-Bi36DgYh.js} +2 -2
- package/dist/{use-restore-focus-D5D9xEjZ.js → use-restore-focus-ZgG5Br63.js} +1 -1
- package/dist/{useAsyncData-BD6AStMt.js → useAsyncData-BD7NLQ3_.js} +1 -1
- package/dist/{useDateFormatter-BXQGu86d.js → useDateFormatter-D8XWr8lL.js} +2 -2
- package/dist/{useDeepCompareMemoize-D_HNdyvI.js → useDeepCompareMemoize-CawMGSRU.js} +1 -1
- package/dist/{useIframeCapabilities-CVtNkI3Y.js → useIframeCapabilities-BUMBOpsM.js} +1 -1
- package/dist/{useLifecycle-DPgiiwwU.js → useLifecycle-3vGhRa1o.js} +3 -3
- package/dist/{useTheme-DzXmYZ5V.js → useTheme-BOw801nZ.js} +2 -2
- package/dist/{vega-component-tFOzgarw.js → vega-component-9siY74NS.js} +9 -9
- package/dist/{vennDiagram-4TSXK5OY-BPHjZo0R.js → vennDiagram-4TSXK5OY-DY8CySdO.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-6fTLYBuD.js → wardleyDiagram-VM6X3IG4-DYC1AFni.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-BYjwjrzE.js → xychartDiagram-S5SC5T6Z-Df8LLnuY.js} +1 -1
- package/package.json +1 -1
- package/src/components/editor/actions/__tests__/useSetOutputsExpanded.test.ts +42 -0
- package/src/components/editor/actions/useNotebookActions.tsx +20 -0
- package/src/components/editor/actions/useSetOutputsExpanded.ts +67 -0
- package/src/core/edit-app.tsx +16 -0
- package/src/core/hotkeys/hotkeys.ts +12 -0
- package/src/plugins/impl/FileBrowserPlugin.tsx +45 -15
- package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +85 -2
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
|
|
2
|
-
import { g as t, h as n, r } from "./button-
|
|
2
|
+
import { g as t, h as n, r } from "./button-B0Xgl-Qn.js";
|
|
3
3
|
import { t as i } from "./react-ofJbxM4x.js";
|
|
4
4
|
import { t as a } from "./compiler-runtime-Cj8ZWiNS.js";
|
|
5
|
-
import { g as o } from "./tooltip-
|
|
5
|
+
import { g as o } from "./tooltip-Bi36DgYh.js";
|
|
6
6
|
import { t as s } from "./jsx-runtime-DgDPJV9S.js";
|
|
7
|
-
import { E as c, w as l } from "./useTheme-
|
|
7
|
+
import { E as c, w as l } from "./useTheme-BOw801nZ.js";
|
|
8
8
|
var u = o("calendar", [
|
|
9
9
|
["path", {
|
|
10
10
|
d: "M8 2v4",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { a as e, r as t } from "./rolldown-runtime-Cb76FRpK.js";
|
|
2
|
-
import { a as n, g as r, s as i } from "./button-
|
|
2
|
+
import { a as n, g as r, s as i } from "./button-B0Xgl-Qn.js";
|
|
3
3
|
import { t as a } from "./react-ofJbxM4x.js";
|
|
4
4
|
import { t as o } from "./compiler-runtime-Cj8ZWiNS.js";
|
|
5
|
-
import { C as s, F as c, L as l, M as u, O as d, P as f, S as p, j as m, v as h } from "./use-restore-focus-
|
|
5
|
+
import { C as s, F as c, L as l, M as u, O as d, P as f, S as p, j as m, v as h } from "./use-restore-focus-ZgG5Br63.js";
|
|
6
6
|
import { l as g, t as _ } from "./_baseMerge-BiGRwuqv.js";
|
|
7
7
|
//#region ../node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/mergeWith.js
|
|
8
8
|
var v = g(function(e, t, n, r) {
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
|
|
2
|
-
import { g as t, h as n, m as r, o as i } from "./button-
|
|
2
|
+
import { g as t, h as n, m as r, o as i } from "./button-B0Xgl-Qn.js";
|
|
3
3
|
import { t as a } from "./react-ofJbxM4x.js";
|
|
4
4
|
import { t as o } from "./compiler-runtime-Cj8ZWiNS.js";
|
|
5
|
-
import { t as s } from "./tooltip-
|
|
6
|
-
import { T as c, c as l } from "./toDate-
|
|
5
|
+
import { t as s } from "./tooltip-Bi36DgYh.js";
|
|
6
|
+
import { T as c, c as l } from "./toDate-DWVp6uE1.js";
|
|
7
7
|
import { t as u } from "./jsx-runtime-DgDPJV9S.js";
|
|
8
|
-
import { n as d } from "./error-banner-
|
|
8
|
+
import { n as d } from "./error-banner-DzAmP0AQ.js";
|
|
9
9
|
import { t as f } from "./debounce-C3jyATpc.js";
|
|
10
|
-
import { A as p, n as ee } from "./useTheme-
|
|
10
|
+
import { A as p, n as ee } from "./useTheme-BOw801nZ.js";
|
|
11
11
|
import { c as m } from "./arrays-CN1djC4k.js";
|
|
12
12
|
import { t as te } from "./objects-CzUgoMIF.js";
|
|
13
|
-
import { t as h } from "./useAsyncData-
|
|
14
|
-
import { a as ne, i as re, n as ie, t as g } from "./formats-
|
|
13
|
+
import { t as h } from "./useAsyncData-BD7NLQ3_.js";
|
|
14
|
+
import { a as ne, i as re, n as ie, t as g } from "./formats-Q9gwlLyD.js";
|
|
15
15
|
import { M as ae, n as oe } from "./vega-loader.browser-DNST6uVf.js";
|
|
16
16
|
import { t as se } from "./react-vega-ewj7q_U5.js";
|
|
17
|
-
import { a as ce, n as _ } from "./loader-
|
|
18
|
-
import { t as le } from "./useDeepCompareMemoize-
|
|
17
|
+
import { a as ce, n as _ } from "./loader-C9OUkMrj.js";
|
|
18
|
+
import { t as le } from "./useDeepCompareMemoize-CawMGSRU.js";
|
|
19
19
|
import { t as ue } from "./semaphore-89jsoHtS.js";
|
|
20
20
|
import { t as de } from "./use-vega-container-remeasure-4DwJbPWu.js";
|
|
21
21
|
//#region src/plugins/impl/vega/fix-relative-url.ts
|
|
@@ -3,7 +3,7 @@ import { t } from "./src-CA_m4ONl.js";
|
|
|
3
3
|
import { n } from "./chunk-Y2CYZVJY-DKKS1VyV.js";
|
|
4
4
|
import { $ as r, H as i, K as a, U as o, a as s, b as c, c as l, et as u, f as d, nt as f, tt as p, v as m, w as h, y as g } from "./chunk-DU6HZSFF-1TwWR6gh.js";
|
|
5
5
|
import { t as _ } from "./rough.esm-CtV9AnV1.js";
|
|
6
|
-
import { p as v } from "./mermaid-
|
|
6
|
+
import { p as v } from "./mermaid-Dfvntf3s.js";
|
|
7
7
|
//#region ../node_modules/.pnpm/khroma@2.1.0/node_modules/khroma/dist/methods/transparentize.js
|
|
8
8
|
var y = (e, t) => p(e, "a", -t), b = 1e-10;
|
|
9
9
|
function x(e, t) {
|
|
@@ -2,7 +2,7 @@ import { i as e } from "./chunk-75Z2AOVW-C4AOleKf.js";
|
|
|
2
2
|
import { n as t } from "./src-CA_m4ONl.js";
|
|
3
3
|
import { n } from "./chunk-Y2CYZVJY-DKKS1VyV.js";
|
|
4
4
|
import { D as r, H as i, K as a, U as o, a as s, b as c, c as l, v as u, w as d, x as f, y as p } from "./chunk-DU6HZSFF-1TwWR6gh.js";
|
|
5
|
-
import { p as m } from "./mermaid-
|
|
5
|
+
import { p as m } from "./mermaid-Dfvntf3s.js";
|
|
6
6
|
import { n as h } from "./mermaid-parser.core-DxuSfYwJ.js";
|
|
7
7
|
import { t as g } from "./chunk-JWPE2WC7-DJvv1t8U.js";
|
|
8
8
|
//#region ../node_modules/.pnpm/mermaid@11.17.2/node_modules/mermaid/dist/chunks/mermaid.core/wardleyDiagram-VM6X3IG4.mjs
|
|
@@ -9,7 +9,7 @@ import { n as s } from "./chunk-Y2CYZVJY-DKKS1VyV.js";
|
|
|
9
9
|
import { D as c, H as l, K as u, U as d, a as f, b as p, c as m, f as h, v as g, w as _, y as v, z as y } from "./chunk-DU6HZSFF-1TwWR6gh.js";
|
|
10
10
|
import "./chunk-PWAF6VOD-lNxB9k8z.js";
|
|
11
11
|
import { t as b } from "./chunk-GMAD6QVW-vStrMz2P.js";
|
|
12
|
-
import { p as x } from "./mermaid-
|
|
12
|
+
import { p as x } from "./mermaid-Dfvntf3s.js";
|
|
13
13
|
//#region ../node_modules/.pnpm/d3-scale@4.0.2/node_modules/d3-scale/src/band.js
|
|
14
14
|
function S() {
|
|
15
15
|
var e = t().unknown(void 0), r = e.domain, i = e.range, o = 0, s = 1, c, l, u = !1, d = 0, f = 0, p = .5;
|
package/package.json
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { MockNotebook } from "@/__mocks__/notebook";
|
|
5
|
+
import type { OutputMessage } from "@/core/kernel/messages";
|
|
6
|
+
import { outputExpansionConfigs } from "../useSetOutputsExpanded";
|
|
7
|
+
|
|
8
|
+
const output: OutputMessage = {
|
|
9
|
+
channel: "output",
|
|
10
|
+
mimetype: "text/plain",
|
|
11
|
+
data: "hello",
|
|
12
|
+
timestamp: 0,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe("outputExpansionConfigs", () => {
|
|
16
|
+
const [withOutput, withoutOutput, expanded, expandedWithoutOutput] =
|
|
17
|
+
MockNotebook.cellIds();
|
|
18
|
+
const notebook = MockNotebook.notebookState({
|
|
19
|
+
cellData: {
|
|
20
|
+
[withOutput]: {},
|
|
21
|
+
[withoutOutput]: {},
|
|
22
|
+
[expanded]: { config: { expand_output: true } },
|
|
23
|
+
[expandedWithoutOutput]: { config: { expand_output: true } },
|
|
24
|
+
},
|
|
25
|
+
cellRuntime: {
|
|
26
|
+
[withOutput]: { output },
|
|
27
|
+
[expanded]: { output },
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("expands clamped cells with output and skips cells without output", () => {
|
|
32
|
+
expect(outputExpansionConfigs(notebook, true)).toEqual({
|
|
33
|
+
[withOutput]: { expand_output: true },
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("clamps expanded cells with output and skips cells without output", () => {
|
|
38
|
+
expect(outputExpansionConfigs(notebook, false)).toEqual({
|
|
39
|
+
[expanded]: { expand_output: false },
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
CheckIcon,
|
|
8
8
|
ChevronDownCircleIcon,
|
|
9
9
|
ChevronRightCircleIcon,
|
|
10
|
+
ChevronsDownUpIcon,
|
|
11
|
+
ChevronsUpDownIcon,
|
|
10
12
|
ClipboardCopyIcon,
|
|
11
13
|
CodeIcon,
|
|
12
14
|
CommandIcon,
|
|
@@ -100,6 +102,7 @@ import type { ActionButton } from "./types";
|
|
|
100
102
|
import { useCopyNotebook } from "./useCopyNotebook";
|
|
101
103
|
import { useRestartKernel } from "./useRestartKernel";
|
|
102
104
|
import { useSetCodeVisibility } from "./useSetCodeVisibility";
|
|
105
|
+
import { useSetOutputsExpanded } from "./useSetOutputsExpanded";
|
|
103
106
|
|
|
104
107
|
const NOOP_HANDLER = (event?: Event) => {
|
|
105
108
|
event?.preventDefault();
|
|
@@ -118,6 +121,7 @@ export function useNotebookActions({
|
|
|
118
121
|
const [viewState] = useAtom(viewStateAtom);
|
|
119
122
|
const kioskMode = useAtomValue(kioskModeAtom);
|
|
120
123
|
const setCodeVisibility = useSetCodeVisibility();
|
|
124
|
+
const setOutputsExpanded = useSetOutputsExpanded();
|
|
121
125
|
const [resolvedConfig] = useResolvedMarimoConfig();
|
|
122
126
|
const capabilities = useAtomValue(capabilitiesAtom);
|
|
123
127
|
const aiEnabled = useAtomValue(aiEnabledAtom);
|
|
@@ -596,6 +600,22 @@ export function useNotebookActions({
|
|
|
596
600
|
handle: () => setCodeVisibility(true, "markdown"),
|
|
597
601
|
redundant: true,
|
|
598
602
|
},
|
|
603
|
+
{
|
|
604
|
+
icon: <ChevronsUpDownIcon size={14} strokeWidth={1.5} />,
|
|
605
|
+
label: "Expand all outputs",
|
|
606
|
+
hotkey: "global.expandAllOutputs",
|
|
607
|
+
hidden: viewState.mode !== "edit",
|
|
608
|
+
handle: () => setOutputsExpanded(true),
|
|
609
|
+
redundant: true,
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
icon: <ChevronsDownUpIcon size={14} strokeWidth={1.5} />,
|
|
613
|
+
label: "Clamp all outputs",
|
|
614
|
+
hotkey: "global.clampAllOutputs",
|
|
615
|
+
hidden: viewState.mode !== "edit",
|
|
616
|
+
handle: () => setOutputsExpanded(false),
|
|
617
|
+
redundant: true,
|
|
618
|
+
},
|
|
599
619
|
{
|
|
600
620
|
icon: <ChevronRightCircleIcon size={14} strokeWidth={1.5} />,
|
|
601
621
|
label: "Collapse all sections",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
import {
|
|
5
|
+
getNotebook,
|
|
6
|
+
type NotebookState,
|
|
7
|
+
useCellActions,
|
|
8
|
+
} from "@/core/cells/cells";
|
|
9
|
+
import type { CellId } from "@/core/cells/ids";
|
|
10
|
+
import { isOutputEmpty } from "@/core/cells/outputs";
|
|
11
|
+
import { useRequestClient } from "@/core/network/requests";
|
|
12
|
+
import type { CellConfig } from "@/core/network/types";
|
|
13
|
+
import { Objects } from "@/utils/objects";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The `expand_output` config changes needed to expand or clamp every output.
|
|
17
|
+
*
|
|
18
|
+
* Both actions skip cells without output, so their source isn't annotated
|
|
19
|
+
* with a config that has no effect.
|
|
20
|
+
*/
|
|
21
|
+
export function outputExpansionConfigs(
|
|
22
|
+
notebook: NotebookState,
|
|
23
|
+
expanded: boolean,
|
|
24
|
+
): Record<CellId, Partial<CellConfig>> {
|
|
25
|
+
const configs: Record<CellId, Partial<CellConfig>> = {};
|
|
26
|
+
for (const cellId of notebook.cellIds.inOrderIds) {
|
|
27
|
+
const cell = notebook.cellData[cellId];
|
|
28
|
+
if (
|
|
29
|
+
cell === undefined ||
|
|
30
|
+
(cell.config.expand_output ?? false) === expanded
|
|
31
|
+
) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (isOutputEmpty(notebook.cellRuntime[cellId]?.output)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
configs[cellId] = { expand_output: expanded };
|
|
38
|
+
}
|
|
39
|
+
return configs;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Hook returning a callback that shows every cell's output in full, or clamps
|
|
44
|
+
* every output to a fixed height, by setting each cell's `expand_output` config.
|
|
45
|
+
*/
|
|
46
|
+
export const useSetOutputsExpanded = () => {
|
|
47
|
+
const { updateCellConfig } = useCellActions();
|
|
48
|
+
const { saveCellConfig } = useRequestClient();
|
|
49
|
+
|
|
50
|
+
return useCallback(
|
|
51
|
+
async (expanded: boolean) => {
|
|
52
|
+
const newConfigs = outputExpansionConfigs(getNotebook(), expanded);
|
|
53
|
+
|
|
54
|
+
const entries = Objects.entries(newConfigs);
|
|
55
|
+
if (entries.length === 0) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await saveCellConfig({ configs: newConfigs });
|
|
60
|
+
|
|
61
|
+
for (const [cellId, config] of entries) {
|
|
62
|
+
updateCellConfig({ cellId, config });
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
[updateCellConfig, saveCellConfig],
|
|
66
|
+
);
|
|
67
|
+
};
|
package/src/core/edit-app.tsx
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
useRunStaleCells,
|
|
24
24
|
} from "../components/editor/cell/useRunCells";
|
|
25
25
|
import { useSetCodeVisibility } from "../components/editor/actions/useSetCodeVisibility";
|
|
26
|
+
import { useSetOutputsExpanded } from "../components/editor/actions/useSetOutputsExpanded";
|
|
26
27
|
import { CellArray } from "../components/editor/renderers/cell-array";
|
|
27
28
|
import { CellsRenderer } from "../components/editor/renderers/cells-renderer";
|
|
28
29
|
import { useHotkey } from "../hooks/useHotkey";
|
|
@@ -115,6 +116,7 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
115
116
|
const runAllCells = useRunAllCells();
|
|
116
117
|
const togglePresenting = useTogglePresenting();
|
|
117
118
|
const setCodeVisibility = useSetCodeVisibility();
|
|
119
|
+
const setOutputsExpanded = useSetOutputsExpanded();
|
|
118
120
|
|
|
119
121
|
// HOTKEYS
|
|
120
122
|
useHotkey("global.runStale", () => {
|
|
@@ -141,6 +143,20 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
141
143
|
useHotkey("global.hideAllMarkdownCode", () => {
|
|
142
144
|
setCodeVisibility(true, "markdown");
|
|
143
145
|
});
|
|
146
|
+
useHotkey(
|
|
147
|
+
"global.expandAllOutputs",
|
|
148
|
+
() => {
|
|
149
|
+
setOutputsExpanded(true);
|
|
150
|
+
},
|
|
151
|
+
{ disabled: !isEditing },
|
|
152
|
+
);
|
|
153
|
+
useHotkey(
|
|
154
|
+
"global.clampAllOutputs",
|
|
155
|
+
() => {
|
|
156
|
+
setOutputsExpanded(false);
|
|
157
|
+
},
|
|
158
|
+
{ disabled: !isEditing },
|
|
159
|
+
);
|
|
144
160
|
useHotkey("global.collapseAllSections", () => {
|
|
145
161
|
collapseAllCells();
|
|
146
162
|
});
|
|
@@ -389,6 +389,18 @@ const DEFAULT_HOT_KEY = {
|
|
|
389
389
|
group: "Editing",
|
|
390
390
|
key: NOT_SET,
|
|
391
391
|
},
|
|
392
|
+
"global.expandAllOutputs": {
|
|
393
|
+
name: "Expand all outputs",
|
|
394
|
+
group: "Editing",
|
|
395
|
+
key: NOT_SET,
|
|
396
|
+
additionalKeywords: ["show full output", "unclamp"],
|
|
397
|
+
},
|
|
398
|
+
"global.clampAllOutputs": {
|
|
399
|
+
name: "Clamp all outputs",
|
|
400
|
+
group: "Editing",
|
|
401
|
+
key: NOT_SET,
|
|
402
|
+
additionalKeywords: ["collapse outputs", "shrink outputs"],
|
|
403
|
+
},
|
|
392
404
|
"global.collapseAllSections": {
|
|
393
405
|
name: "Collapse all sections",
|
|
394
406
|
group: "Editing",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import { type LucideIcon, CornerLeftUp } from "lucide-react";
|
|
3
|
+
import { type LucideIcon, ChevronRightIcon, CornerLeftUp } from "lucide-react";
|
|
4
4
|
import { type JSX, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import {
|
|
@@ -12,7 +12,6 @@ import { Spinner } from "@/components/icons/spinner";
|
|
|
12
12
|
import { Button } from "@/components/ui/button";
|
|
13
13
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
14
14
|
import { Label } from "@/components/ui/label";
|
|
15
|
-
import { NativeSelect } from "@/components/ui/native-select";
|
|
16
15
|
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
|
|
17
16
|
import { toast } from "@/components/ui/use-toast";
|
|
18
17
|
import { RANDOM_ID_ATTR } from "@/core/dom/ui-element-constants";
|
|
@@ -472,12 +471,19 @@ export const FileBrowser = ({
|
|
|
472
471
|
//
|
|
473
472
|
// Assumes that path contains at least one delimiter, which is true
|
|
474
473
|
// only if this is an absolute path.
|
|
475
|
-
const { parentDirectories } = getProtocolAndParentDirectories({
|
|
474
|
+
const { protocol, parentDirectories } = getProtocolAndParentDirectories({
|
|
476
475
|
path,
|
|
477
476
|
delimiter,
|
|
478
477
|
initialPath,
|
|
479
478
|
restrictNavigation,
|
|
480
479
|
});
|
|
480
|
+
const breadcrumbs = parentDirectories.toReversed().map((dir) => ({
|
|
481
|
+
path: dir,
|
|
482
|
+
name:
|
|
483
|
+
dir === protocol
|
|
484
|
+
? protocol
|
|
485
|
+
: (dir.split(delimiter).findLast(Boolean) ?? dir),
|
|
486
|
+
}));
|
|
481
487
|
|
|
482
488
|
const selectionKindLabel =
|
|
483
489
|
selectionMode === "all"
|
|
@@ -515,18 +521,42 @@ export const FileBrowser = ({
|
|
|
515
521
|
<div>
|
|
516
522
|
{error && <Banner kind="danger">{error.message}</Banner>}
|
|
517
523
|
{renderHeader()}
|
|
518
|
-
<
|
|
519
|
-
className="
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
524
|
+
<nav aria-label="Current directory" className="mt-2">
|
|
525
|
+
<ol className="flex flex-wrap items-center gap-0.5 text-sm">
|
|
526
|
+
{breadcrumbs.map((crumb, index) => {
|
|
527
|
+
const isCurrent = index === breadcrumbs.length - 1;
|
|
528
|
+
return (
|
|
529
|
+
<li key={crumb.path} className="flex items-center gap-0.5">
|
|
530
|
+
{index > 0 && (
|
|
531
|
+
<ChevronRightIcon
|
|
532
|
+
size={14}
|
|
533
|
+
className="text-muted-foreground shrink-0"
|
|
534
|
+
aria-hidden={true}
|
|
535
|
+
/>
|
|
536
|
+
)}
|
|
537
|
+
<button
|
|
538
|
+
type="button"
|
|
539
|
+
title={crumb.path}
|
|
540
|
+
aria-current={isCurrent ? "page" : undefined}
|
|
541
|
+
onClick={() => {
|
|
542
|
+
if (!isCurrent) {
|
|
543
|
+
setNewPath(crumb.path);
|
|
544
|
+
}
|
|
545
|
+
}}
|
|
546
|
+
className={cn(
|
|
547
|
+
"rounded px-1 focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring",
|
|
548
|
+
isCurrent
|
|
549
|
+
? "font-medium cursor-default"
|
|
550
|
+
: "text-muted-foreground hover:bg-accent hover:text-foreground",
|
|
551
|
+
)}
|
|
552
|
+
>
|
|
553
|
+
{crumb.name}
|
|
554
|
+
</button>
|
|
555
|
+
</li>
|
|
556
|
+
);
|
|
557
|
+
})}
|
|
558
|
+
</ol>
|
|
559
|
+
</nav>
|
|
530
560
|
|
|
531
561
|
{data && typeof data.total_count === "number" && (
|
|
532
562
|
<div className="text-xs text-muted-foreground mt-1 px-1">
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
fireEvent,
|
|
5
|
+
render,
|
|
6
|
+
screen,
|
|
7
|
+
waitFor,
|
|
8
|
+
within,
|
|
9
|
+
} from "@testing-library/react";
|
|
4
10
|
import { beforeAll, describe, expect, it, vi } from "vitest";
|
|
5
11
|
import { SetupMocks } from "@/__mocks__/common";
|
|
6
12
|
import { initialModeAtom } from "@/core/mode";
|
|
@@ -187,7 +193,7 @@ describe("FileBrowserPlugin keyboard accessibility", () => {
|
|
|
187
193
|
const docsRow = docs.closest('[role="row"]')!;
|
|
188
194
|
fireEvent.keyDown(docsRow, { key: "ArrowDown" }); // move active off the parent
|
|
189
195
|
fireEvent.click(docs); // navigate into "docs"
|
|
190
|
-
await screen.findByText("docs"); // listing reloads (mock returns same files)
|
|
196
|
+
await within(screen.getByRole("grid")).findByText("docs"); // listing reloads (mock returns same files)
|
|
191
197
|
const rows = screen.getAllByRole("row");
|
|
192
198
|
expect(rows[0]).toHaveAttribute("tabindex", "0");
|
|
193
199
|
// the focused row must match the only tabbable row
|
|
@@ -361,3 +367,80 @@ describe("FileBrowserPlugin keyboard accessibility", () => {
|
|
|
361
367
|
function rowFor(name: string): HTMLElement {
|
|
362
368
|
return screen.getByText(name).closest('[role="row"]') as HTMLElement;
|
|
363
369
|
}
|
|
370
|
+
|
|
371
|
+
describe("FileBrowserPlugin breadcrumbs", () => {
|
|
372
|
+
function breadcrumbs() {
|
|
373
|
+
return within(
|
|
374
|
+
screen.getByRole("navigation", { name: "Current directory" }),
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
it("renders a crumb per ancestor with the current directory last", async () => {
|
|
379
|
+
renderBrowser();
|
|
380
|
+
await screen.findByText("docs");
|
|
381
|
+
const items = breadcrumbs().getAllByRole("listitem");
|
|
382
|
+
expect(items.map((li) => li.textContent)).toEqual(["/", "home", "user"]);
|
|
383
|
+
expect(breadcrumbs().getByRole("button", { name: "user" })).toHaveAttribute(
|
|
384
|
+
"aria-current",
|
|
385
|
+
"page",
|
|
386
|
+
);
|
|
387
|
+
expect(
|
|
388
|
+
breadcrumbs().getByRole("button", { name: "home" }),
|
|
389
|
+
).not.toHaveAttribute("aria-current");
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("keeps focus on a crumb after navigating to it", async () => {
|
|
393
|
+
const { listDirectory } = renderBrowser();
|
|
394
|
+
await screen.findByText("docs");
|
|
395
|
+
const home = breadcrumbs().getByRole("button", { name: "home" });
|
|
396
|
+
home.focus();
|
|
397
|
+
fireEvent.click(home);
|
|
398
|
+
await waitFor(() =>
|
|
399
|
+
expect(listDirectory).toHaveBeenLastCalledWith({ path: "/home" }),
|
|
400
|
+
);
|
|
401
|
+
const current = breadcrumbs().getByRole("button", { name: "home" });
|
|
402
|
+
expect(current).toHaveAttribute("aria-current", "page");
|
|
403
|
+
expect(current).toBe(home);
|
|
404
|
+
expect(current).toHaveFocus();
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("does not reload when the current crumb is clicked", async () => {
|
|
408
|
+
const { listDirectory } = renderBrowser();
|
|
409
|
+
await screen.findByText("docs");
|
|
410
|
+
fireEvent.click(breadcrumbs().getByRole("button", { name: "user" }));
|
|
411
|
+
expect(listDirectory).toHaveBeenCalledTimes(1);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("navigates to an ancestor when a crumb is clicked", async () => {
|
|
415
|
+
const { listDirectory } = renderBrowser();
|
|
416
|
+
await screen.findByText("docs");
|
|
417
|
+
fireEvent.click(breadcrumbs().getByRole("button", { name: "home" }));
|
|
418
|
+
await waitFor(() =>
|
|
419
|
+
expect(listDirectory).toHaveBeenLastCalledWith({ path: "/home" }),
|
|
420
|
+
);
|
|
421
|
+
fireEvent.click(breadcrumbs().getByRole("button", { name: "/" }));
|
|
422
|
+
await waitFor(() =>
|
|
423
|
+
expect(listDirectory).toHaveBeenLastCalledWith({ path: "/" }),
|
|
424
|
+
);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("hides ancestors outside the initial path when navigation is restricted", async () => {
|
|
428
|
+
render(
|
|
429
|
+
<FileBrowser
|
|
430
|
+
initialPath="/home/user"
|
|
431
|
+
filetypes={[]}
|
|
432
|
+
selectionMode="all"
|
|
433
|
+
multiple={true}
|
|
434
|
+
label={null}
|
|
435
|
+
restrictNavigation={true}
|
|
436
|
+
value={[]}
|
|
437
|
+
setValue={vi.fn()}
|
|
438
|
+
host={document.createElement("div")}
|
|
439
|
+
list_directory={mockListDirectory(FILES)}
|
|
440
|
+
/>,
|
|
441
|
+
);
|
|
442
|
+
await screen.findByText("docs");
|
|
443
|
+
const items = breadcrumbs().getAllByRole("listitem");
|
|
444
|
+
expect(items.map((li) => li.textContent)).toEqual(["user"]);
|
|
445
|
+
});
|
|
446
|
+
});
|