@marimo-team/frontend 0.25.2-dev0 → 0.25.2-dev10
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/assets/{ConnectedDataExplorerComponent-DJ5jwKfp.js → ConnectedDataExplorerComponent-BFDHLGyp.js} +1 -1
- package/dist/assets/{ErrorBoundary-BeE1cdgM.js → ErrorBoundary-CtGZ1MAN.js} +2 -2
- package/dist/assets/{LazyAnyLanguageCodeMirror-B_IYTCX5.js → LazyAnyLanguageCodeMirror-CaEYnB8x.js} +2 -2
- package/dist/assets/{RunButton-C2kFUhyO.js → RunButton-YN1KKz4a.js} +1 -1
- package/dist/assets/{abnfDiagram-VCTEODGH-CSPJSGaM.js → abnfDiagram-VCTEODGH-CqPJWAgx.js} +1 -1
- package/dist/assets/{add-cell-with-ai-BFgdVPaN.js → add-cell-with-ai-Dceg80EG.js} +1 -1
- package/dist/assets/{agent-panel-Dh7yWh9P.js → agent-panel-CEG8eS3F.js} +1 -1
- package/dist/assets/{ai-model-dropdown-DJ2elMLM.js → ai-model-dropdown-SOxGsjRY.js} +1 -1
- package/dist/assets/{alert-dialog-CeE3i3Qa.js → alert-dialog-BsB0FYEL.js} +1 -1
- package/dist/assets/{any-language-editor-Cj_HzlZY.js → any-language-editor-b9spizfh.js} +1 -1
- package/dist/assets/{app-config-button-DBCTefOX.js → app-config-button-CTTg_TIX.js} +1 -1
- package/dist/assets/{architectureDiagram-5GKGNRK7-7iukRoC9.js → architectureDiagram-5GKGNRK7-DXjBUUE6.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-cuhpQB60.js → blockDiagram-I7D4REHJ-DS6I1u-N.js} +1 -1
- package/dist/assets/{button-BoklZTGL.js → button-qFeU84ax.js} +1 -1
- package/dist/assets/{cache-panel-C0oB1kE2.js → cache-panel-Ct9EFgP7.js} +1 -1
- package/dist/assets/{capabilities-Bj3s1hGN.js → capabilities-BqUNf14U.js} +1 -1
- package/dist/assets/{cells-CzugpTQu.js → cells-CU9huD49.js} +1 -1
- package/dist/assets/{chat-display-DevO4F8W.js → chat-display-DWPnai8M.js} +1 -1
- package/dist/assets/{chat-panel-BGw0bqDk.js → chat-panel-DGnu6v96.js} +1 -1
- package/dist/assets/{chat-ui-CZZFGlZi.js → chat-ui-BqLN7iW4.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-BIILwBhp.js → chunk-IMKFNOWR-s8kQsL8K.js} +1 -1
- package/dist/assets/{chunk-SHT3W25Y-BtLGdeDB.js → chunk-SHT3W25Y-CM7-8w1-.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-B0UfxhUP.js → chunk-SVP7TREG-BErtDMhh.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-BP7vel_P.js → chunk-TICWLB2K-CGVR3aYq.js} +1 -1
- package/dist/assets/{circle-CeQt3rdW.js → circle-CzTMKZwz.js} +1 -1
- package/dist/assets/{classDiagram-ZZMXUADV-CoNXKNMZ.js → classDiagram-ZZMXUADV-isldMhyu.js} +1 -1
- package/dist/assets/{classDiagram-v2-VYDZK3BY-CoNXKNMZ.js → classDiagram-v2-VYDZK3BY-isldMhyu.js} +1 -1
- package/dist/assets/{code-visibility-BFNYI7hr.js → code-visibility-BGq9NyQN.js} +1 -1
- package/dist/assets/{command-palette-DN4JoOLD.js → command-palette-CyBHUvlK.js} +1 -1
- package/dist/assets/{common-DUCPhoyP.js → common-CdJQaC_v.js} +1 -1
- package/dist/assets/{components-CK7CeYlo.js → components-DsJLlugL.js} +1 -1
- package/dist/assets/{components-DAmFBASm.js → components-zfCkpDjF.js} +1 -1
- package/dist/assets/{config-B4VJJibu.js → config-2Nts63h2.js} +1 -1
- package/dist/assets/{config-BxZgoFvi.js → config-BdH5RvM0.js} +1 -1
- package/dist/assets/{config-DpGPGznL.js → config-D7CP5wR5.js} +1 -1
- package/dist/assets/{copy-BhJktFeI.js → copy-B--GzTEX.js} +1 -1
- package/dist/assets/{copy-icon-DFhUGBjO.js → copy-icon-DQuttmjH.js} +1 -1
- package/dist/assets/{createReducer-BPvK7C_R.js → createReducer-CI8rlZE9.js} +1 -1
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-BARZRQBb.js → cynefinDiagram-5FMLGOSQ-B7iZDq2a.js} +1 -1
- package/dist/assets/{dagre-DVyyr7Cb.js → dagre-DkKsNZNe.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-79s0lOP0.js → dagre-GXQ25YYZ-CLT8-BB_.js} +1 -1
- package/dist/assets/{data-grid-overlay-editor-DPfnIudL.js → data-grid-overlay-editor-Bt4aaHfB.js} +1 -1
- package/dist/assets/{dependency-graph-panel-BN4CLayU.js → dependency-graph-panel-DPFdz5rd.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-BCviGhvd.js → diagram-S7CK7UJ4-SuzEYncV.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-Ds-BT46T.js → diagram-UQ7AKVKN-cuBTTzq8.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-BtL2eh0t.js → diagram-VX7I27RA-h6SkZKIl.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-C6SmbLBS.js → diagram-Z3DM3KII-C-Vebae0.js} +1 -1
- package/dist/assets/{dist-CyYN7zJg.js → dist-DFI04FfI.js} +1 -1
- package/dist/assets/{documentation-panel-CzRbXV5u.js → documentation-panel-YTTKKIe_.js} +1 -1
- package/dist/assets/{dropdown-menu-VuyoeQOD.js → dropdown-menu-Crx6knLg.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-D_JU4cMQ.js → ebnfDiagram-PWID7BFC-Bm_qbW1t.js} +1 -1
- package/dist/assets/{edit-page-jGNYBqQo.js → edit-page-CWbb1Sdk.js} +45 -45
- package/dist/assets/{erDiagram-RLTQ6QDP-Z9MJ6X-r.js → erDiagram-RLTQ6QDP-Dwwt91bq.js} +1 -1
- package/dist/assets/{error-entries-CDSsy76p.js → error-entries-DvgFKvod.js} +1 -1
- package/dist/assets/{error-panel-ABvjFG3p.js → error-panel-D1XBOiHc.js} +1 -1
- package/dist/assets/{feedback-button-CZhLiwkO.js → feedback-button-CXjDdARJ.js} +1 -1
- package/dist/assets/{file-explorer-panel-DTiEpmVE.js → file-explorer-panel-BKJMBA3O.js} +1 -1
- package/dist/assets/{file-name-input-C1C8xrjA.js → file-name-input-Bd3Q-WUK.js} +1 -1
- package/dist/assets/{filename-fxG7K_MU.js → filename-rhW-R6K2.js} +1 -1
- package/dist/assets/{flowDiagram-HODETNUW-ncLciK2K.js → flowDiagram-HODETNUW-CqLwY5NX.js} +1 -1
- package/dist/assets/{fullscreen-BmzAqMKq.js → fullscreen-3wS6IaIC.js} +1 -1
- package/dist/assets/{gallery-page-Moi4ZdJ0.js → gallery-page-CrkxyZTA.js} +1 -1
- package/dist/assets/{glide-data-editor-CbsCWu-K.js → glide-data-editor-B5WqL5gm.js} +4 -4
- package/dist/assets/{graphlib-3maKwbRO.js → graphlib-6Yb0YQgW.js} +1 -1
- package/dist/assets/has-B4C7ywdy.js +1 -0
- package/dist/assets/{home-page-UAgVOkCZ.js → home-page-BNKKAvoF.js} +1 -1
- package/dist/assets/{index-DafnX1Xx.js → index-C9FPQNuH.js} +8 -8
- package/dist/assets/{infoDiagram-27XIBGKW-Dk8U81nJ.js → infoDiagram-27XIBGKW-dRXMROiS.js} +1 -1
- package/dist/assets/{input-BDakodZ2.js → input-D4bMj5Nb.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-CzHQlMUQ.js → ishikawaDiagram-5VMMS53U-CvBCDJJe.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-CVyVYiI6.js → kanban-definition-UXKFOSKX-fIKpyolW.js} +1 -1
- package/dist/assets/{logs-panel-Bz5ysVX4.js → logs-panel-adAvx6o3.js} +1 -1
- package/dist/assets/{menu-items-BBdeXCvU.js → menu-items-D-vbZmqL.js} +1 -1
- package/dist/assets/{mermaid-D_jPUVqz.js → mermaid-DpSqZGNM.js} +4 -4
- package/dist/assets/{mindmap-definition-YA3MSWOX-D5t6Aq5Z.js → mindmap-definition-YA3MSWOX-B95LGKLt.js} +1 -1
- package/dist/assets/{number-overlay-editor-FAD8sjFU.js → number-overlay-editor-Bs5xzKX-.js} +1 -1
- package/dist/assets/{numbers-DsgsDslU.js → numbers-DyJNv6Vk.js} +1 -1
- package/dist/assets/{outline-panel-85oiQ0XX.js → outline-panel-B9u6Wgol.js} +1 -1
- package/dist/assets/{packages-panel-ClKIaP5S.js → packages-panel-D-g71io7.js} +1 -1
- package/dist/assets/{panels-B48zp96D.js → panels-C5xQNcZv.js} +3 -3
- package/dist/assets/{pegDiagram-XKGWAZYB-BTkTm4ej.js → pegDiagram-XKGWAZYB-CMaSc5Ln.js} +1 -1
- package/dist/assets/{pieDiagram-E7YTZNPT-Bq6X88M1.js → pieDiagram-E7YTZNPT-CZY0tLgh.js} +1 -1
- package/dist/assets/{process-output-Cg2ta3vY.js → process-output-CpF79y4D.js} +1 -1
- package/dist/assets/{railroadDiagram-O6MQD6OU-fv8HXW_g.js → railroadDiagram-O6MQD6OU-BSMijs6s.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-DDOOMUfC.js → requirementDiagram-BXWQKSXE-DjzkUUZk.js} +1 -1
- package/dist/assets/{reveal-component-CR43gpTZ.js → reveal-component-ZyDayfDQ.js} +1 -1
- package/dist/assets/{run-page-NR_KFWk8.js → run-page-Ba_64fL5.js} +1 -1
- package/dist/assets/{scratchpad-panel-DHc-oVb1.js → scratchpad-panel-7bxRkpPb.js} +1 -1
- package/dist/assets/{scroll-to-bottom-button-BSCkDXet.js → scroll-to-bottom-button-BLNi0TGS.js} +1 -1
- package/dist/assets/{secrets-panel-C-65ZjnM.js → secrets-panel-DmKgIV5x.js} +1 -1
- package/dist/assets/{select-BplWpfFo.js → select-CWHHoM53.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-DdpBhQaS.js → sequenceDiagram-WJ2MYXX4-Dp7JdkJ6.js} +1 -1
- package/dist/assets/{session-panel-DiwQqIbt.js → session-panel-D9qVJogC.js} +1 -1
- package/dist/assets/{session-DXKMv4Bs.js → session-ztCnZJHI.js} +1 -1
- package/dist/assets/{snippets-panel-BLmXrneD.js → snippets-panel-QdZvjlBm.js} +1 -1
- package/dist/assets/{state-Ozzur_f2.js → state-0NZOvqJz.js} +1 -1
- package/dist/assets/{stateDiagram-D77RDMKH-D3p19fac.js → stateDiagram-D77RDMKH-CR5QDK_-.js} +1 -1
- package/dist/assets/{stateDiagram-v2-MP3YSRHH-CplsDaDl.js → stateDiagram-v2-MP3YSRHH-BmMYN-vz.js} +1 -1
- package/dist/assets/{strings-BHZPE0Mc.js → strings-LjhpYwmx.js} +1 -1
- package/dist/assets/{swimlanes-42K2YHIH-CLxCSvU4.js → swimlanes-42K2YHIH-By_1jYHM.js} +1 -1
- package/dist/assets/{swimlanesDiagram-VR7AAH4N-DPoIFx8T.js → swimlanesDiagram-VR7AAH4N-DjMyvNFc.js} +1 -1
- package/dist/assets/{swiper-component-rqJChT_P.js → swiper-component-Cbtej5b-.js} +1 -1
- package/dist/assets/{terminal-CY4h1UmO.js → terminal-Ri8br8XX.js} +1 -1
- package/dist/assets/{timeline-definition-24CTP7MA-Cw2PY--x.js → timeline-definition-24CTP7MA-BjNxu6yh.js} +1 -1
- package/dist/assets/{toast-KNfsFUiB.js → toast-DXkezxWI.js} +1 -1
- package/dist/assets/{tooltip-PLkI_UfK.js → tooltip-BQ9qHrrN.js} +1 -1
- package/dist/assets/{tooltip-DR2Z2N4w.js → tooltip-CamoSiGF.js} +1 -1
- package/dist/assets/{tracing-Q8NAdlrA.js → tracing-BxMH9HGb.js} +1 -1
- package/dist/assets/{tracing-panel-Ci4kY4dr.js → tracing-panel-EeL3md5x.js} +2 -2
- package/dist/assets/{use-restore-focus-B98SEiGc.js → use-restore-focus-D0UtBPD6.js} +1 -1
- package/dist/assets/{useCellActionButton-DpYtVfFe.js → useCellActionButton-Di4NkDbT.js} +1 -1
- package/dist/assets/{useEventListener-BdZUApHd.js → useEventListener-CUPWI_1X.js} +1 -1
- package/dist/assets/{useInterval-ChchS0Rl.js → useInterval-DluQa5sq.js} +1 -1
- package/dist/assets/{useTheme-DcnXlv0_.js → useTheme-CnWB5G1X.js} +1 -1
- package/dist/assets/{vega-component-BpoSew4K.js → vega-component-DGTXJwmx.js} +1 -1
- package/dist/assets/{vega-embed-container-BOJG8mCA.js → vega-embed-container-Din5FYpB.js} +1 -1
- package/dist/assets/{vennDiagram-4TSXK5OY-D8bN6lVl.js → vennDiagram-4TSXK5OY-BR66J2hx.js} +1 -1
- package/dist/assets/{wardleyDiagram-VM6X3IG4-CbZdo0SA.js → wardleyDiagram-VM6X3IG4-Kg0-lASD.js} +1 -1
- package/dist/assets/{write-secret-modal-BiG2tahJ.js → write-secret-modal-wFgr-1dA.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-DUUQ9tau.js → xychartDiagram-S5SC5T6Z-BSXFWSRW.js} +1 -1
- package/dist/assets/{youtube-A9VT7NHb.js → youtube-Ck_NlGi9.js} +1 -1
- package/dist/index.html +31 -31
- package/package.json +7 -7
- package/src/components/data-table/range-focus/__tests__/atoms.test.ts +11 -11
- 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/components/tracing/tracing.test.tsx +30 -30
- 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
- package/dist/assets/has-cJsIKK9t.js +0 -1
|
@@ -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
|
+
};
|
|
@@ -5,36 +5,36 @@ import { formatChartTime } from "./utils";
|
|
|
5
5
|
describe("formatChartTime", () => {
|
|
6
6
|
beforeAll(() => {
|
|
7
7
|
// Mock Date to always use UTC
|
|
8
|
-
vi.spyOn(global.Date.prototype, "getFullYear").mockImplementation(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
);
|
|
13
|
-
vi.spyOn(global.Date.prototype, "getMonth").mockImplementation(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
);
|
|
18
|
-
vi.spyOn(global.Date.prototype, "getDate").mockImplementation(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
);
|
|
23
|
-
vi.spyOn(global.Date.prototype, "getHours").mockImplementation(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
);
|
|
28
|
-
vi.spyOn(global.Date.prototype, "getMinutes").mockImplementation(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
);
|
|
33
|
-
vi.spyOn(global.Date.prototype, "getSeconds").mockImplementation(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
);
|
|
8
|
+
vi.spyOn(global.Date.prototype, "getFullYear").mockImplementation(function (
|
|
9
|
+
this: Date,
|
|
10
|
+
) {
|
|
11
|
+
return this.getUTCFullYear();
|
|
12
|
+
});
|
|
13
|
+
vi.spyOn(global.Date.prototype, "getMonth").mockImplementation(function (
|
|
14
|
+
this: Date,
|
|
15
|
+
) {
|
|
16
|
+
return this.getUTCMonth();
|
|
17
|
+
});
|
|
18
|
+
vi.spyOn(global.Date.prototype, "getDate").mockImplementation(function (
|
|
19
|
+
this: Date,
|
|
20
|
+
) {
|
|
21
|
+
return this.getUTCDate();
|
|
22
|
+
});
|
|
23
|
+
vi.spyOn(global.Date.prototype, "getHours").mockImplementation(function (
|
|
24
|
+
this: Date,
|
|
25
|
+
) {
|
|
26
|
+
return this.getUTCHours();
|
|
27
|
+
});
|
|
28
|
+
vi.spyOn(global.Date.prototype, "getMinutes").mockImplementation(function (
|
|
29
|
+
this: Date,
|
|
30
|
+
) {
|
|
31
|
+
return this.getUTCMinutes();
|
|
32
|
+
});
|
|
33
|
+
vi.spyOn(global.Date.prototype, "getSeconds").mockImplementation(function (
|
|
34
|
+
this: Date,
|
|
35
|
+
) {
|
|
36
|
+
return this.getUTCSeconds();
|
|
37
|
+
});
|
|
38
38
|
vi.spyOn(global.Date.prototype, "getMilliseconds").mockImplementation(
|
|
39
39
|
function (this: Date) {
|
|
40
40
|
return this.getUTCMilliseconds();
|
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
|
+
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{Wa as e}from"./index-DafnX1Xx.js";var t=Object.prototype.hasOwnProperty;function n(e,n){return e!=null&&t.call(e,n)}function r(t,r){return t!=null&&e(t,r,n)}export{r as t};
|