@marimo-team/frontend 0.25.3-dev129 → 0.25.3-dev134
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/{CellStatus-6xDEj3Hs.js → CellStatus-DC6FFQOg.js} +1 -1
- package/dist/assets/{ConnectedDataExplorerComponent-DCcLAktu.js → ConnectedDataExplorerComponent-BV1mVpaE.js} +1 -1
- package/dist/assets/{ErrorBoundary-98Xo04DS.js → ErrorBoundary-Ce-Ujlwj.js} +2 -2
- package/dist/assets/{JsonOutput-V47aiTIK.js → JsonOutput-CfEBzaqW.js} +4 -4
- package/dist/assets/{MarimoErrorOutput-DeWZNBgL.js → MarimoErrorOutput-Bh6o9zle.js} +1 -1
- package/dist/assets/{RenderHTML-Bjvcs70-.js → RenderHTML-COGZsXQV.js} +1 -1
- package/dist/assets/{RunButton-CnvvXD9d.js → RunButton-DQ51plUv.js} +1 -1
- package/dist/assets/{add-cell-with-ai-BbuqzITi.js → add-cell-with-ai-D7gtGUsg.js} +1 -1
- package/dist/assets/{add-connection-dialog-DIC7owvZ.js → add-connection-dialog-CpMCc97S.js} +1 -1
- package/dist/assets/{agent-panel-D8hO2P1x.js → agent-panel-BSFYqJ9B.js} +2 -2
- package/dist/assets/{ai-model-dropdown-DgmiG_5j.js → ai-model-dropdown-D575Vid8.js} +3 -3
- package/dist/assets/{app-config-button-UZHv1Iim.js → app-config-button-Cfcg_5Bz.js} +1 -1
- package/dist/assets/{blob-BPknzzkY.js → blob-CszMu60N.js} +1 -1
- package/dist/assets/{cell-editor-CM-K1_wq.js → cell-editor-BLpOlLnM.js} +2 -2
- package/dist/assets/cell-link-Cj0gGaMV.js +1 -0
- package/dist/assets/{cells-BypTg035.js → cells-SvbLY10y.js} +1 -1
- package/dist/assets/{chat-display-BRLNOoTF.js → chat-display-D43_g3TX.js} +1 -1
- package/dist/assets/{chat-panel-D02Rk9we.js → chat-panel-B_HAJMwx.js} +1 -1
- package/dist/assets/{chat-ui-DFN_RwxF.js → chat-ui-BEcYCphu.js} +2 -2
- package/dist/assets/{column-preview-85ORMhfB.js → column-preview-CrzaJKk8.js} +1 -1
- package/dist/assets/{command-palette-CtyjjdsA.js → command-palette-Bm9yIVcD.js} +1 -1
- package/dist/assets/{common-J265II7i.js → common-BJyUxCzv.js} +1 -1
- package/dist/assets/{components-Bd3Dpi92.js → components-D1f_Cz4i.js} +1 -1
- package/dist/assets/{datasource-Dy4rzzSP.js → datasource-C8KSVfSD.js} +1 -1
- package/dist/assets/{dependency-graph-panel-ByGFFT4E.js → dependency-graph-panel-BhmyJbUl.js} +1 -1
- package/dist/assets/{documentation-panel-LxuJCY3M.js → documentation-panel-DcVG0Q0d.js} +1 -1
- package/dist/assets/{download-BsBJWH_j.js → download-Bx52VRE4.js} +1 -1
- package/dist/assets/{edit-page--d6X-OO9.js → edit-page-DUrwOHR-.js} +6 -6
- package/dist/assets/{error-entries-0wTSSuK9.js → error-entries-11OuHzUF.js} +1 -1
- package/dist/assets/{error-panel-ZNdbE3Fo.js → error-panel-XJy5S25F.js} +1 -1
- package/dist/assets/{feedback-button-C6asPmsh.js → feedback-button-B7jh_B4w.js} +1 -1
- package/dist/assets/feedback-button-C-eKtxXE.js +1 -0
- package/dist/assets/{file-explorer-panel-0fUP_ptL.js → file-explorer-panel-CnVxbF4A.js} +1 -1
- package/dist/assets/{file-name-input-B8lzI5iS.js → file-name-input-BJuqjQqH.js} +2 -2
- package/dist/assets/floating-outline-8yyZ-MOJ.js +1 -0
- package/dist/assets/{gallery-page-ClR8U-km.js → gallery-page-DJvGHePf.js} +1 -1
- package/dist/assets/{glide-data-editor-B6QQBQwS.js → glide-data-editor-Cyc8DHZx.js} +1 -1
- package/dist/assets/{home-page-CJnqDcT9.js → home-page-tRZJmSU6.js} +1 -1
- package/dist/assets/{hooks-CCek4i9f.js → hooks-CxGwybGw.js} +1 -1
- package/dist/assets/index-CoxbqhGH.js +30 -0
- package/dist/assets/index-u08m0hQE.css +2 -0
- package/dist/assets/{layout-66Urh4Zg.js → layout-DvXJxBiL.js} +5 -5
- package/dist/assets/{logs-panel-DFhkIcge.js → logs-panel-KtsY7TrR.js} +1 -1
- package/dist/assets/{markdown-renderer-mYYQYD2m.js → markdown-renderer-DUgV7_fm.js} +1 -1
- package/dist/assets/{name-cell-input-Crcu3i2z.js → name-cell-input-C3-Cj-Ek.js} +1 -1
- package/dist/assets/{outline-panel-BFIPK0H_.js → outline-panel-BzyY4JA6.js} +1 -1
- package/dist/assets/{packages-panel-B4KtjgkF.js → packages-panel-DxosbWko.js} +1 -1
- package/dist/assets/{panel-accordion-state-D6KtwSF8.js → panel-accordion-state-BRbTib2B.js} +1 -1
- package/dist/assets/panels-Da3071Q4.js +1 -0
- package/dist/assets/{process-output-DDvMd6Jh.js → process-output-CSwx7rvo.js} +1 -1
- package/dist/assets/{readonly-code-display-VDqYn2d0.js → readonly-code-display-B9y9423Y.js} +1 -1
- package/dist/assets/{readonly-python-code-CZUKLXJ4.js → readonly-python-code-Dgw6trlk.js} +1 -1
- package/dist/assets/{reveal-component-BMmDU1uN.js → reveal-component-B9A4wkG-.js} +1 -1
- package/dist/assets/{run-page-BmOPPh2Y.js → run-page-C1MQ5b_A.js} +1 -1
- package/dist/assets/{runtime-details-state-CipqynUD.js → runtime-details-state-D_hOQL9T.js} +1 -1
- package/dist/assets/scratchpad-panel-CBCsPmMW.js +1 -0
- package/dist/assets/secrets-panel-ConQpchl.js +1 -0
- package/dist/assets/session-panel-CiHCKPlx.js +1 -0
- package/dist/assets/snippets-panel-C0VDE_4i.js +1 -0
- package/dist/assets/{state-QfGh3m2G.js → state-DrraMmqz.js} +1 -1
- package/dist/assets/{state-BLGvzMB6.js → state-DvU2AiFx.js} +22 -22
- package/dist/assets/tracing-C5d51Nmi.js +1 -0
- package/dist/assets/{tracing-panel-CsHuzRGP.js → tracing-panel-B89R8dWE.js} +2 -2
- package/dist/assets/{tree-actions-BQw_utcj.js → tree-actions-Cph8FVzh.js} +1 -1
- package/dist/assets/{useAddCell-_b8InOZC.js → useAddCell-VPkB72pn.js} +1 -1
- package/dist/assets/{useCellActionButton-BbgMzBdk.js → useCellActionButton--zI3GFnt.js} +1 -1
- package/dist/assets/{useDeleteCell-DhoFbEkU.js → useDeleteCell-CjewKeSr.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-COTULhoH.js → useDependencyPanelTab-Bt_ka-wl.js} +1 -1
- package/dist/assets/{useNotebookActions-xnA_l3nY.js → useNotebookActions-DV2S6KaG.js} +1 -1
- package/dist/assets/{useRunCells-VBCe6CLn.js → useRunCells-BupCZpzk.js} +1 -1
- package/dist/assets/{useSplitCell-AvSpVAmq.js → useSplitCell-CaLIR0TS.js} +1 -1
- package/dist/index.html +21 -21
- package/package.json +1 -1
- package/src/__tests__/module-entry.test.ts +32 -0
- package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
- package/src/components/ai/ai-model-dropdown.tsx +11 -6
- package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
- package/src/components/chat/acp/blocks.tsx +1 -0
- package/src/components/chat/acp/session-tabs.tsx +8 -3
- package/src/components/data-table/TableTopBar.tsx +2 -0
- package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
- package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
- package/src/components/data-table/data-table.tsx +6 -0
- package/src/components/data-table/range-focus/atoms.ts +4 -6
- package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
- package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
- package/src/components/data-table/renderers.tsx +16 -7
- package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
- package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
- package/src/components/datasources/datasources.tsx +1 -0
- package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
- package/src/components/editor/cell/collapse.tsx +5 -3
- package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
- package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
- package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
- package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
- package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -1
- package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +4 -4
- package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
- package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
- package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +4 -4
- package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
- package/src/components/editor/file-tree/file-explorer.tsx +35 -3
- package/src/components/editor/file-tree/file-name-input.tsx +1 -0
- package/src/components/editor/header/status.tsx +28 -5
- package/src/components/editor/links/__tests__/cell-link.test.tsx +146 -0
- package/src/components/editor/links/cell-link.tsx +14 -5
- package/src/components/editor/navigation/focus-utils.ts +4 -4
- package/src/components/editor/notebook-cell.tsx +11 -2
- package/src/components/editor/output/MarimoTracebackOutput.tsx +17 -15
- package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
- package/src/components/editor/package-alert.tsx +8 -3
- package/src/components/scratchpad/scratchpad.test.tsx +95 -0
- package/src/components/scratchpad/scratchpad.tsx +14 -1
- package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
- package/src/components/tracing/tracing.tsx +43 -39
- package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
- package/src/components/ui/draggable-popover.tsx +24 -2
- package/src/core/attachments/state.ts +2 -2
- package/src/core/document/__tests__/codemirror.test.ts +3 -1
- package/src/core/document/__tests__/hooks.test.tsx +7 -7
- package/src/core/document/__tests__/model.test.ts +44 -11
- package/src/core/document/__tests__/scenario-fixture.ts +2 -1
- package/src/core/document/__tests__/test-notebook.ts +19 -9
- package/src/core/document/draft.ts +1 -1
- package/src/core/document/index.ts +3 -10
- package/src/core/document/model.ts +14 -11
- package/src/core/document/state.ts +1 -1
- package/src/core/document/sync.ts +24 -18
- package/src/core/host/AGENTS.md +3 -7
- package/src/core/host/__tests__/fixtures.ts +30 -0
- package/src/core/host/__tests__/{notebook-client.test.ts → replica.test.ts} +42 -74
- package/src/core/host/__tests__/wasm.test.ts +8 -0
- package/src/core/host/backend.ts +4 -0
- package/src/core/host/index.ts +14 -0
- package/src/core/host/{notebook-client.ts → replica.ts} +75 -86
- package/src/core/host/static.ts +2 -0
- package/src/core/host/wasm.ts +10 -1
- package/src/core/wasm/bridge.ts +1 -1
- package/src/core/websocket/__tests__/backend.test.ts +111 -22
- package/src/core/websocket/__tests__/useNotebookReplica.hook.test.tsx +141 -0
- package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +4 -4
- package/src/core/websocket/backend.ts +43 -5
- package/src/core/websocket/connection-state.ts +1 -1
- package/src/core/websocket/state.ts +3 -3
- package/src/core/websocket/transports/__tests__/notebook.test.ts +11 -21
- package/src/core/websocket/types.ts +2 -2
- package/src/core/websocket/useNotebookReplica.ts +69 -0
- package/src/core/websocket/useNotebookSession.tsx +6 -6
- package/src/plugins/impl/chat/chat-ui.tsx +2 -1
- package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
- package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
- package/src/plugins/impl/data-frames/panel.tsx +43 -16
- package/dist/assets/cell-link-CAjuFcLK.js +0 -1
- package/dist/assets/feedback-button-Bt89tGG2.js +0 -1
- package/dist/assets/floating-outline-CRpmOv32.js +0 -1
- package/dist/assets/index-B5pWkt7a.js +0 -30
- package/dist/assets/index-BnhSnwvB.css +0 -2
- package/dist/assets/panels-DiwGKPzK.js +0 -1
- package/dist/assets/scratchpad-panel-Drr_kuwb.js +0 -1
- package/dist/assets/secrets-panel-D-W1PL76.js +0 -1
- package/dist/assets/session-panel-W7JOcog_.js +0 -1
- package/dist/assets/snippets-panel-cUPEdm66.js +0 -1
- package/dist/assets/tracing-BNFynaTu.js +0 -1
- package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +0 -228
- package/src/core/websocket/useNotebookClient.ts +0 -89
|
@@ -27,10 +27,12 @@ function mountPackageAlert({
|
|
|
27
27
|
configuredManager,
|
|
28
28
|
sandboxBackend,
|
|
29
29
|
source = "kernel",
|
|
30
|
+
packages = ["polars"],
|
|
30
31
|
}: {
|
|
31
32
|
configuredManager: PackageManagerName;
|
|
32
33
|
sandboxBackend: "uv" | "pixi" | null;
|
|
33
34
|
source?: "kernel" | "server";
|
|
35
|
+
packages?: string[];
|
|
34
36
|
}) {
|
|
35
37
|
const store = createStore();
|
|
36
38
|
const client = MockRequestClient.create();
|
|
@@ -55,7 +57,7 @@ function mountPackageAlert({
|
|
|
55
57
|
packageAlert: {
|
|
56
58
|
id: "missing-package",
|
|
57
59
|
kind: "missing",
|
|
58
|
-
packages
|
|
60
|
+
packages,
|
|
59
61
|
isolated: true,
|
|
60
62
|
source,
|
|
61
63
|
},
|
|
@@ -97,6 +99,25 @@ it("keeps the configured package manager selectable outside a sandbox", () => {
|
|
|
97
99
|
expect(managerSelect).not.toBeDisabled();
|
|
98
100
|
});
|
|
99
101
|
|
|
102
|
+
it("removes an extra absent from available choices through a keyboard menu item", async () => {
|
|
103
|
+
const client = mountPackageAlert({
|
|
104
|
+
configuredManager: "uv",
|
|
105
|
+
sandboxBackend: null,
|
|
106
|
+
packages: ["polars[dev]"],
|
|
107
|
+
});
|
|
108
|
+
fireEvent.keyDown(screen.getByRole("button", { name: "dev" }), {
|
|
109
|
+
key: "ArrowDown",
|
|
110
|
+
});
|
|
111
|
+
const remove = await screen.findByRole("menuitem", { name: "Remove dev" });
|
|
112
|
+
fireEvent.keyDown(remove, { key: "Enter" });
|
|
113
|
+
fireEvent.click(screen.getByTestId("install-packages-button"));
|
|
114
|
+
expect(client.sendInstallMissingPackages).toHaveBeenCalledExactlyOnceWith({
|
|
115
|
+
manager: "uv",
|
|
116
|
+
source: "kernel",
|
|
117
|
+
versions: { polars: "" },
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
100
121
|
it("installs a selected version in a Pixi sandbox", () => {
|
|
101
122
|
const client = mountPackageAlert({
|
|
102
123
|
configuredManager: "uv",
|
|
@@ -67,10 +67,12 @@ export const CollapsedCellBanner: React.FC<{
|
|
|
67
67
|
const states = getDescendantsStatus(notebook, cellId);
|
|
68
68
|
|
|
69
69
|
return (
|
|
70
|
-
<
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
aria-label={`Expand ${count} collapsed ${count === 1 ? "cell" : "cells"}`}
|
|
71
73
|
onClick={onClick}
|
|
72
74
|
className={cn(
|
|
73
|
-
"flex items-center justify-between w-[calc(100%-2rem)] h-9 bg-muted rounded-b mx-4 opacity-80 hover:opacity-100 cursor-pointer",
|
|
75
|
+
"flex items-center justify-between w-[calc(100%-2rem)] h-9 bg-muted rounded-b mx-4 opacity-80 hover:opacity-100 cursor-pointer focus-visible:outline focus-visible:outline-ring",
|
|
74
76
|
)}
|
|
75
77
|
>
|
|
76
78
|
<Toolbar
|
|
@@ -102,7 +104,7 @@ export const CollapsedCellBanner: React.FC<{
|
|
|
102
104
|
</>
|
|
103
105
|
}
|
|
104
106
|
/>
|
|
105
|
-
</
|
|
107
|
+
</button>
|
|
106
108
|
);
|
|
107
109
|
});
|
|
108
110
|
CollapsedCellBanner.displayName = "CollapsedCellBanner";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { describe, expect, it, vi } from "vitest";
|
|
5
|
+
import type { OutlineItem } from "@/core/cells/outline";
|
|
6
|
+
import { MiniMap, OutlineList } from "../floating-outline";
|
|
7
|
+
import { scrollToOutlineItem } from "../useActiveOutline";
|
|
8
|
+
|
|
9
|
+
vi.mock("../useActiveOutline", () => ({ scrollToOutlineItem: vi.fn() }));
|
|
10
|
+
|
|
11
|
+
const items: OutlineItem[] = [
|
|
12
|
+
{ name: "First", level: 1, by: { id: "first" } },
|
|
13
|
+
{ name: "Second", html: "<em>Second</em>", level: 2, by: { id: "second" } },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
describe("outline keyboard navigation", () => {
|
|
17
|
+
it.each([
|
|
18
|
+
{ Component: MiniMap, role: "button" },
|
|
19
|
+
{ Component: OutlineList, role: "link" },
|
|
20
|
+
])(
|
|
21
|
+
"keeps the focused $role when other headings change",
|
|
22
|
+
({ Component, role }) => {
|
|
23
|
+
const duplicate: OutlineItem = {
|
|
24
|
+
name: "Repeated second",
|
|
25
|
+
level: 2,
|
|
26
|
+
by: { id: "second" },
|
|
27
|
+
};
|
|
28
|
+
const initial = [...items, duplicate];
|
|
29
|
+
const props = {
|
|
30
|
+
activeHeaderId: undefined,
|
|
31
|
+
activeOccurrences: undefined,
|
|
32
|
+
};
|
|
33
|
+
const { rerender } = render(<Component {...props} items={initial} />);
|
|
34
|
+
const focused = screen.getByRole(role, { name: "Repeated second" });
|
|
35
|
+
focused.focus();
|
|
36
|
+
|
|
37
|
+
for (const by of [{ id: "inserted" }, { path: "second" }]) {
|
|
38
|
+
const inserted: OutlineItem = { name: "Inserted", level: 1, by };
|
|
39
|
+
rerender(<Component {...props} items={[inserted, ...initial]} />);
|
|
40
|
+
expect(screen.getByRole(role, { name: "Repeated second" })).toBe(
|
|
41
|
+
focused,
|
|
42
|
+
);
|
|
43
|
+
expect(focused).toHaveFocus();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
rerender(
|
|
47
|
+
<Component {...props} items={[items[1], duplicate, items[0]]} />,
|
|
48
|
+
);
|
|
49
|
+
expect(screen.getByRole(role, { name: "Repeated second" })).toBe(focused);
|
|
50
|
+
expect(focused).toHaveFocus();
|
|
51
|
+
fireEvent.click(focused);
|
|
52
|
+
expect(scrollToOutlineItem).toHaveBeenLastCalledWith(duplicate, 1);
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
it("activates plain and rich headings with Enter", () => {
|
|
57
|
+
render(
|
|
58
|
+
<OutlineList
|
|
59
|
+
items={items}
|
|
60
|
+
activeHeaderId={undefined}
|
|
61
|
+
activeOccurrences={undefined}
|
|
62
|
+
/>,
|
|
63
|
+
);
|
|
64
|
+
items.forEach((item) => {
|
|
65
|
+
const link = screen.getByRole("link", { name: item.name });
|
|
66
|
+
expect(link).toHaveAttribute("tabindex", "0");
|
|
67
|
+
fireEvent.keyDown(link, { key: "Enter" });
|
|
68
|
+
expect(scrollToOutlineItem).toHaveBeenLastCalledWith(item, 0);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("names each minimap button", () => {
|
|
73
|
+
render(
|
|
74
|
+
<MiniMap
|
|
75
|
+
items={items}
|
|
76
|
+
activeHeaderId={undefined}
|
|
77
|
+
activeOccurrences={undefined}
|
|
78
|
+
/>,
|
|
79
|
+
);
|
|
80
|
+
const button = screen.getByRole("button", { name: "Second" });
|
|
81
|
+
fireEvent.click(button);
|
|
82
|
+
expect(scrollToOutlineItem).toHaveBeenLastCalledWith(items[1], 0);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -17,6 +17,8 @@ export const FloatingOutline: React.FC = () => {
|
|
|
17
17
|
findOutlineElements(items),
|
|
18
18
|
);
|
|
19
19
|
const [isHovered, setIsHovered] = React.useState(false);
|
|
20
|
+
const [isFocused, setIsFocused] = React.useState(false);
|
|
21
|
+
const isVisible = isHovered || isFocused;
|
|
20
22
|
|
|
21
23
|
// Hide if < 2 items
|
|
22
24
|
// It's kinda useless to have an outline with only one item
|
|
@@ -29,6 +31,12 @@ export const FloatingOutline: React.FC = () => {
|
|
|
29
31
|
<div
|
|
30
32
|
onMouseEnter={() => setIsHovered(true)}
|
|
31
33
|
onMouseLeave={() => setIsHovered(false)}
|
|
34
|
+
onFocus={() => setIsFocused(true)}
|
|
35
|
+
onBlur={(e) => {
|
|
36
|
+
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
37
|
+
setIsFocused(false);
|
|
38
|
+
}
|
|
39
|
+
}}
|
|
32
40
|
className={cn(
|
|
33
41
|
"fixed top-[25vh] right-8 z-10000 print:hidden",
|
|
34
42
|
// Hide on small screens
|
|
@@ -36,9 +44,10 @@ export const FloatingOutline: React.FC = () => {
|
|
|
36
44
|
)}
|
|
37
45
|
>
|
|
38
46
|
<OutlineList
|
|
47
|
+
inert={!isVisible}
|
|
39
48
|
className={cn(
|
|
40
49
|
"-top-4 max-h-[70vh] bg-background rounded-lg shadow-lg absolute overflow-auto transition-all duration-300 w-[300px] border",
|
|
41
|
-
|
|
50
|
+
isVisible ? "left-[-280px] opacity-100" : "left-[300px] opacity-0",
|
|
42
51
|
)}
|
|
43
52
|
items={items}
|
|
44
53
|
activeHeaderId={activeHeaderId}
|
|
@@ -62,17 +71,20 @@ export const MiniMap: React.FC<{
|
|
|
62
71
|
const seen = new Map<string, number>();
|
|
63
72
|
return (
|
|
64
73
|
<div className="flex flex-col gap-4 items-end max-h-[70vh] overflow-hidden">
|
|
65
|
-
{items.map((item
|
|
74
|
+
{items.map((item) => {
|
|
66
75
|
const identifier = "id" in item.by ? item.by.id : item.by.path;
|
|
76
|
+
const selector = JSON.stringify(item.by);
|
|
67
77
|
// Keep track of how many times we've seen this selector
|
|
68
|
-
const occurrences = seen.get(
|
|
69
|
-
seen.set(
|
|
78
|
+
const occurrences = seen.get(selector) ?? 0;
|
|
79
|
+
seen.set(selector, occurrences + 1);
|
|
70
80
|
|
|
71
81
|
return (
|
|
72
|
-
<
|
|
73
|
-
|
|
82
|
+
<button
|
|
83
|
+
type="button"
|
|
84
|
+
aria-label={item.name}
|
|
85
|
+
key={`${selector}-${occurrences}`}
|
|
74
86
|
className={cn(
|
|
75
|
-
"h-[2px] bg-muted-foreground/60",
|
|
87
|
+
"h-[2px] bg-muted-foreground/60 focus-visible:outline focus-visible:outline-ring",
|
|
76
88
|
item.level === 1 && "w-5",
|
|
77
89
|
item.level === 2 && "w-4",
|
|
78
90
|
item.level === 3 && "w-3",
|
|
@@ -91,24 +103,31 @@ export const MiniMap: React.FC<{
|
|
|
91
103
|
|
|
92
104
|
export const OutlineList: React.FC<{
|
|
93
105
|
className?: string;
|
|
106
|
+
inert?: boolean;
|
|
94
107
|
items: OutlineItem[];
|
|
95
108
|
activeHeaderId: string | undefined;
|
|
96
109
|
activeOccurrences: number | undefined;
|
|
97
|
-
}> = ({ items, activeHeaderId, activeOccurrences, className }) => {
|
|
110
|
+
}> = ({ items, activeHeaderId, activeOccurrences, className, inert }) => {
|
|
98
111
|
// Map of selector to its occurrences
|
|
99
112
|
const seen = new Map<string, number>();
|
|
100
113
|
return (
|
|
101
|
-
<div
|
|
102
|
-
{
|
|
114
|
+
<div
|
|
115
|
+
inert={inert}
|
|
116
|
+
className={cn("flex flex-col overflow-auto py-4 pl-2", className)}
|
|
117
|
+
>
|
|
118
|
+
{items.map((item) => {
|
|
103
119
|
const identifier = "id" in item.by ? item.by.id : item.by.path;
|
|
120
|
+
const selector = JSON.stringify(item.by);
|
|
104
121
|
// Keep track of how many times we've seen this selector
|
|
105
|
-
const occurrences = seen.get(
|
|
106
|
-
seen.set(
|
|
122
|
+
const occurrences = seen.get(selector) ?? 0;
|
|
123
|
+
seen.set(selector, occurrences + 1);
|
|
107
124
|
|
|
108
|
-
const key = `${
|
|
125
|
+
const key = `${selector}-${occurrences}`;
|
|
109
126
|
const sharedProps = {
|
|
127
|
+
role: "link",
|
|
128
|
+
tabIndex: 0,
|
|
110
129
|
className: cn(
|
|
111
|
-
"px-2 py-1 cursor-pointer hover:bg-accent/50 hover:text-accent-foreground rounded-l",
|
|
130
|
+
"px-2 py-1 cursor-pointer hover:bg-accent/50 hover:text-accent-foreground rounded-l focus-visible:outline focus-visible:outline-ring",
|
|
112
131
|
item.level === 1 && "font-semibold",
|
|
113
132
|
item.level === 2 && "ml-3",
|
|
114
133
|
item.level === 3 && "ml-6",
|
|
@@ -118,6 +137,12 @@ export const OutlineList: React.FC<{
|
|
|
118
137
|
"text-accent-foreground",
|
|
119
138
|
),
|
|
120
139
|
onClick: () => scrollToOutlineItem(item, occurrences),
|
|
140
|
+
onKeyDown: (e: React.KeyboardEvent) => {
|
|
141
|
+
if (e.target === e.currentTarget && e.key === "Enter") {
|
|
142
|
+
e.preventDefault();
|
|
143
|
+
scrollToOutlineItem(item, occurrences);
|
|
144
|
+
}
|
|
145
|
+
},
|
|
121
146
|
};
|
|
122
147
|
|
|
123
148
|
if (item.html) {
|
|
@@ -113,9 +113,10 @@ export function findOutlineElements(items: OutlineItem[]) {
|
|
|
113
113
|
return items
|
|
114
114
|
.map((item) => {
|
|
115
115
|
const identifier = "id" in item.by ? item.by.id : item.by.path;
|
|
116
|
+
const selector = JSON.stringify(item.by);
|
|
116
117
|
// Keep track of how many times we've seen this selector
|
|
117
|
-
const occurrences = seen.get(
|
|
118
|
-
seen.set(
|
|
118
|
+
const occurrences = seen.get(selector) ?? 0;
|
|
119
|
+
seen.set(selector, occurrences + 1);
|
|
119
120
|
|
|
120
121
|
const el = findOutlineItem(item, occurrences);
|
|
121
122
|
if (!el) {
|
|
@@ -66,6 +66,7 @@ const SecretsPanel: React.FC = () => {
|
|
|
66
66
|
<div className="flex justify-start h-8 border-b">
|
|
67
67
|
<button
|
|
68
68
|
type="button"
|
|
69
|
+
aria-label="Add environment variable"
|
|
69
70
|
className="border-r px-2 m-0 h-full hover:bg-accent hover:text-accent-foreground"
|
|
70
71
|
onClick={() =>
|
|
71
72
|
openModal(
|
|
@@ -71,7 +71,11 @@ const SnippetsPanel: React.FC = () => {
|
|
|
71
71
|
rootClassName="flex-1 border-r"
|
|
72
72
|
/>
|
|
73
73
|
<ContributeSnippetButton>
|
|
74
|
-
<button
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
aria-label="Contribute snippet"
|
|
77
|
+
className="float-right px-2 m-0 h-full hover:bg-accent hover:text-accent-foreground"
|
|
78
|
+
>
|
|
75
79
|
<PlusIcon className="h-4 w-4" />
|
|
76
80
|
</button>
|
|
77
81
|
</ContributeSnippetButton>
|
|
@@ -16,14 +16,14 @@ import { TooltipProvider } from "@/components/ui/tooltip";
|
|
|
16
16
|
import { toast } from "@/components/ui/use-toast";
|
|
17
17
|
import type { Attachment } from "@/core/attachments/state";
|
|
18
18
|
import { pairConfigAtom } from "@/core/config/pair";
|
|
19
|
-
import {
|
|
19
|
+
import { createNotebookReplica } from "@/core/host/replica";
|
|
20
20
|
import {
|
|
21
21
|
type StableSessionId,
|
|
22
22
|
stableSessionIdAtom,
|
|
23
23
|
} from "@/core/kernel/session";
|
|
24
24
|
import { API } from "@/core/network/api";
|
|
25
25
|
import { connectionAtom } from "@/core/network/connection";
|
|
26
|
-
import {
|
|
26
|
+
import { notebookReplicaAtom } from "@/core/websocket/state";
|
|
27
27
|
import { ConnectionState } from "@/core/websocket/types";
|
|
28
28
|
import { Deferred } from "@/utils/Deferred";
|
|
29
29
|
import { AttachmentStatus } from "../footer-items/attachment-status";
|
|
@@ -45,8 +45,8 @@ function renderStatus(attachments: Attachment[] = [AGENT], preview = true) {
|
|
|
45
45
|
const store = createStore();
|
|
46
46
|
store.set(connectionAtom, { state: ConnectionState.OPEN });
|
|
47
47
|
store.set(pairConfigAtom, { ...PAIR_CONFIG, hostEnabled: preview });
|
|
48
|
-
store.set(
|
|
49
|
-
...
|
|
48
|
+
store.set(notebookReplicaAtom, {
|
|
49
|
+
...createNotebookReplica(store),
|
|
50
50
|
connectionAtom: atom({ state: ConnectionState.OPEN }),
|
|
51
51
|
attachmentsAtom,
|
|
52
52
|
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { act, fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { createStore, Provider } from "jotai";
|
|
5
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
7
|
+
import { userConfigAtom } from "@/core/config/config";
|
|
8
|
+
import { defaultUserConfig } from "@/core/config/config-schema";
|
|
9
|
+
import { getDefaultHotkey } from "@/core/hotkeys/hotkeys";
|
|
10
|
+
import {
|
|
11
|
+
KeyboardShortcuts,
|
|
12
|
+
keyboardShortcutsAtom,
|
|
13
|
+
} from "../keyboard-shortcuts";
|
|
14
|
+
|
|
15
|
+
const saveUserConfig = vi.hoisted(() => vi.fn().mockResolvedValue(undefined));
|
|
16
|
+
vi.mock("@/core/network/requests", () => ({
|
|
17
|
+
useRequestClient: () => ({ saveUserConfig }),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
describe("shortcut recorder keyboard navigation", () => {
|
|
21
|
+
beforeEach(() => vi.clearAllMocks());
|
|
22
|
+
|
|
23
|
+
it("allows Tab navigation and Reset without saving a shortcut", async () => {
|
|
24
|
+
const store = createStore();
|
|
25
|
+
const config = defaultUserConfig();
|
|
26
|
+
store.set(userConfigAtom, {
|
|
27
|
+
...config,
|
|
28
|
+
keymap: { ...config.keymap, overrides: { "cell.run": "Ctrl-9" } },
|
|
29
|
+
});
|
|
30
|
+
store.set(keyboardShortcutsAtom, true);
|
|
31
|
+
render(
|
|
32
|
+
<Provider store={store}>
|
|
33
|
+
<TooltipProvider>
|
|
34
|
+
<KeyboardShortcuts />
|
|
35
|
+
</TooltipProvider>
|
|
36
|
+
</Provider>,
|
|
37
|
+
);
|
|
38
|
+
const edit = screen.getByRole("button", {
|
|
39
|
+
name: `Edit shortcut: ${getDefaultHotkey("cell.run").name}`,
|
|
40
|
+
});
|
|
41
|
+
act(() => edit.focus());
|
|
42
|
+
fireEvent.click(edit, { detail: 0 });
|
|
43
|
+
const recorder = screen.getByPlaceholderText(
|
|
44
|
+
getDefaultHotkey("cell.run").name,
|
|
45
|
+
);
|
|
46
|
+
expect(recorder).toHaveFocus();
|
|
47
|
+
expect(fireEvent.keyDown(recorder, { key: "Tab" })).toBe(true);
|
|
48
|
+
expect(fireEvent.keyDown(recorder, { key: "Tab", shiftKey: true })).toBe(
|
|
49
|
+
true,
|
|
50
|
+
);
|
|
51
|
+
expect(recorder).toBeInTheDocument();
|
|
52
|
+
expect(saveUserConfig).not.toHaveBeenCalled();
|
|
53
|
+
const reset = screen.getByRole("button", { name: /^Reset to default:/ });
|
|
54
|
+
act(() => reset.focus());
|
|
55
|
+
expect(reset).toHaveFocus();
|
|
56
|
+
await act(async () => fireEvent.click(reset, { detail: 0 }));
|
|
57
|
+
expect(saveUserConfig).toHaveBeenCalledExactlyOnceWith({
|
|
58
|
+
config: { keymap: { ...config.keymap, overrides: {} } },
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -134,6 +134,9 @@ export const KeyboardShortcuts: React.FC = () => {
|
|
|
134
134
|
defaultValue={newShortcut.join("+")}
|
|
135
135
|
placeholder={hotkey.name}
|
|
136
136
|
onKeyDown={(e) => {
|
|
137
|
+
if (e.key === "Tab" && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
137
140
|
e.preventDefault();
|
|
138
141
|
const next: string[] = [];
|
|
139
142
|
|
|
@@ -189,6 +192,7 @@ export const KeyboardShortcuts: React.FC = () => {
|
|
|
189
192
|
autoFocus={true}
|
|
190
193
|
endAdornment={
|
|
191
194
|
<Button
|
|
195
|
+
aria-label="Cancel shortcut recording"
|
|
192
196
|
variant="text"
|
|
193
197
|
size="xs"
|
|
194
198
|
className="mb-0"
|
|
@@ -206,13 +210,14 @@ export const KeyboardShortcuts: React.FC = () => {
|
|
|
206
210
|
Press a key combination
|
|
207
211
|
</span>
|
|
208
212
|
{defaultHotkey.key !== hotkey.key && (
|
|
209
|
-
<
|
|
213
|
+
<button
|
|
214
|
+
type="button"
|
|
210
215
|
className="text-xs cursor-pointer text-primary"
|
|
211
216
|
onClick={handleResetShortcut}
|
|
212
217
|
>
|
|
213
218
|
Reset to default:{" "}
|
|
214
219
|
<span className="font-mono">{defaultHotkey.key}</span>
|
|
215
|
-
</
|
|
220
|
+
</button>
|
|
216
221
|
)}
|
|
217
222
|
</div>
|
|
218
223
|
</div>
|
|
@@ -228,13 +233,17 @@ export const KeyboardShortcuts: React.FC = () => {
|
|
|
228
233
|
className="grid grid-cols-[auto_2fr_3fr] gap-2 items-center"
|
|
229
234
|
>
|
|
230
235
|
{hotkeys.isEditable(action) ? (
|
|
231
|
-
<
|
|
232
|
-
|
|
236
|
+
<button
|
|
237
|
+
type="button"
|
|
238
|
+
aria-label={`Edit shortcut: ${hotkey.name}`}
|
|
239
|
+
className="cursor-pointer opacity-60 hover:opacity-100 text-muted-foreground w-3 h-3 focus-visible:outline focus-visible:outline-ring"
|
|
233
240
|
onClick={() => {
|
|
234
241
|
setNewShortcut([]);
|
|
235
242
|
setEditingShortcut(action);
|
|
236
243
|
}}
|
|
237
|
-
|
|
244
|
+
>
|
|
245
|
+
<EditIcon className="w-3 h-3" />
|
|
246
|
+
</button>
|
|
238
247
|
) : (
|
|
239
248
|
<div className="w-3 h-3" />
|
|
240
249
|
)}
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from "@/core/cells/error-report";
|
|
26
26
|
import { pairConfigAtom } from "@/core/config/pair";
|
|
27
27
|
import { notebookStateFromSession } from "@/core/cells/session";
|
|
28
|
-
import {
|
|
28
|
+
import { createNotebookReplica } from "@/core/host/replica";
|
|
29
29
|
import type { MarimoError, OutputMessage } from "@/core/kernel/messages";
|
|
30
30
|
import {
|
|
31
31
|
type StableSessionId,
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
} from "@/core/kernel/session";
|
|
34
34
|
import { API } from "@/core/network/api";
|
|
35
35
|
import { connectionAtom } from "@/core/network/connection";
|
|
36
|
-
import {
|
|
36
|
+
import { notebookReplicaAtom } from "@/core/websocket/state";
|
|
37
37
|
import { ConnectionState } from "@/core/websocket/types";
|
|
38
38
|
import { Deferred } from "@/utils/Deferred";
|
|
39
39
|
import { HTTPError } from "@/utils/errors";
|
|
@@ -97,8 +97,8 @@ function renderButton({
|
|
|
97
97
|
const store = createStore();
|
|
98
98
|
store.set(connectionAtom, { state: ConnectionState.OPEN });
|
|
99
99
|
store.set(pairConfigAtom, { ...PAIR_CONFIG, hostEnabled: preview });
|
|
100
|
-
store.set(
|
|
101
|
-
...
|
|
100
|
+
store.set(notebookReplicaAtom, {
|
|
101
|
+
...createNotebookReplica(store),
|
|
102
102
|
connectionAtom: atom({ state: ConnectionState.OPEN }),
|
|
103
103
|
attachmentsAtom,
|
|
104
104
|
});
|
|
@@ -17,6 +17,7 @@ import { TooltipProvider } from "@/components/ui/tooltip";
|
|
|
17
17
|
import { requestClientAtom } from "@/core/network/requests";
|
|
18
18
|
import type { FileInfo, FileSearchResponse } from "@/core/network/types";
|
|
19
19
|
import { Deferred } from "@/utils/Deferred";
|
|
20
|
+
import { openNotebook } from "@/utils/links";
|
|
20
21
|
import type { FilePath } from "@/utils/paths";
|
|
21
22
|
import { TreeDndProvider } from "../dnd-wrapper";
|
|
22
23
|
import { FileExplorer } from "../file-explorer";
|
|
@@ -29,6 +30,11 @@ vi.mock("../file-viewer", () => ({
|
|
|
29
30
|
FileViewer: ({ file }: { file: FileInfo }) => <div>Preview: {file.name}</div>,
|
|
30
31
|
}));
|
|
31
32
|
|
|
33
|
+
vi.mock("@/utils/links", async (importOriginal) => ({
|
|
34
|
+
...(await importOriginal<typeof import("@/utils/links")>()),
|
|
35
|
+
openNotebook: vi.fn(),
|
|
36
|
+
}));
|
|
37
|
+
|
|
32
38
|
function file(name: string, isDirectory = false): FileInfo {
|
|
33
39
|
return {
|
|
34
40
|
id: name,
|
|
@@ -91,6 +97,47 @@ beforeEach(() => {
|
|
|
91
97
|
});
|
|
92
98
|
|
|
93
99
|
describe("file browser navigation", () => {
|
|
100
|
+
it("tabs into the notebook action, returns to its row, and exits the tree", async () => {
|
|
101
|
+
client.sendListFiles.mockResolvedValue({
|
|
102
|
+
root: "/workspace",
|
|
103
|
+
files: [
|
|
104
|
+
{ ...file("notebook.py"), isMarimoFile: true },
|
|
105
|
+
file("notes.txt"),
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
render(
|
|
109
|
+
<>
|
|
110
|
+
<FileExplorer height={300} />
|
|
111
|
+
<button type="button">After tree</button>
|
|
112
|
+
</>,
|
|
113
|
+
{ wrapper },
|
|
114
|
+
);
|
|
115
|
+
const row = await screen.findByRole("treeitem", { name: "notebook.py" });
|
|
116
|
+
act(() => row.focus());
|
|
117
|
+
fireEvent.keyDown(row, { key: "Tab" });
|
|
118
|
+
const open = screen.getByRole("button", {
|
|
119
|
+
name: "Open notebook notebook.py",
|
|
120
|
+
});
|
|
121
|
+
expect(open).toHaveFocus();
|
|
122
|
+
fireEvent.keyDown(open, { key: "Tab", shiftKey: true });
|
|
123
|
+
expect(row).toHaveFocus();
|
|
124
|
+
fireEvent.keyDown(row, { key: "Tab" });
|
|
125
|
+
for (const key of ["Enter", " "]) {
|
|
126
|
+
expect(fireEvent.keyDown(open, { key })).toBe(true);
|
|
127
|
+
expect(
|
|
128
|
+
screen.queryByText("Preview: notebook.py"),
|
|
129
|
+
).not.toBeInTheDocument();
|
|
130
|
+
}
|
|
131
|
+
fireEvent.click(open);
|
|
132
|
+
expect(openNotebook).toHaveBeenCalledExactlyOnceWith("notebook.py");
|
|
133
|
+
fireEvent.keyDown(open, { key: "Tab" });
|
|
134
|
+
expect(screen.getByRole("button", { name: "After tree" })).toHaveFocus();
|
|
135
|
+
const plain = screen.getByRole("treeitem", { name: "notes.txt" });
|
|
136
|
+
act(() => plain.focus());
|
|
137
|
+
fireEvent.keyDown(plain, { key: "Tab" });
|
|
138
|
+
expect(screen.getByRole("button", { name: "After tree" })).toHaveFocus();
|
|
139
|
+
});
|
|
140
|
+
|
|
94
141
|
it("debounces the first character, subsequent edits, and searches after clearing", async () => {
|
|
95
142
|
render(<FileExplorer height={300} />, { wrapper });
|
|
96
143
|
const input = await screen.findByRole("textbox", {
|
|
@@ -263,6 +263,27 @@ export const FileExplorer: React.FC<{
|
|
|
263
263
|
event.target instanceof Element
|
|
264
264
|
? event.target.getAttribute("role")
|
|
265
265
|
: null;
|
|
266
|
+
if (event.key === "Tab" && event.target instanceof HTMLElement) {
|
|
267
|
+
if (!event.shiftKey && role === "treeitem") {
|
|
268
|
+
const action = event.target.querySelector<HTMLButtonElement>(
|
|
269
|
+
'[data-file-tree-action="open"]',
|
|
270
|
+
);
|
|
271
|
+
if (action) {
|
|
272
|
+
event.preventDefault();
|
|
273
|
+
event.stopPropagation();
|
|
274
|
+
action.focus();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
} else if (
|
|
278
|
+
event.shiftKey &&
|
|
279
|
+
event.target.dataset.fileTreeAction === "open"
|
|
280
|
+
) {
|
|
281
|
+
event.preventDefault();
|
|
282
|
+
event.stopPropagation();
|
|
283
|
+
event.target.closest<HTMLElement>('[role="treeitem"]')?.focus();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
266
287
|
if (query.trim() || (role !== "treeitem" && role !== "tree")) {
|
|
267
288
|
return;
|
|
268
289
|
}
|
|
@@ -637,13 +658,24 @@ const Show = ({
|
|
|
637
658
|
<span className="flex-1 overflow-hidden text-ellipsis">
|
|
638
659
|
{node.data.name}
|
|
639
660
|
{node.data.isMarimoFile && node.data.isPrimaryRoot && !isWasm() && (
|
|
640
|
-
<
|
|
661
|
+
<button
|
|
662
|
+
type="button"
|
|
663
|
+
aria-label={`Open notebook ${node.data.name}`}
|
|
641
664
|
data-testid="file-explorer-open-marimo-button"
|
|
642
|
-
|
|
665
|
+
data-file-tree-action="open"
|
|
666
|
+
onKeyDown={(event) => {
|
|
667
|
+
if (event.key !== "Tab") {
|
|
668
|
+
event.stopPropagation();
|
|
669
|
+
}
|
|
670
|
+
}}
|
|
671
|
+
className={cn(
|
|
672
|
+
"shrink-0 ml-2 text-sm hidden group-hover:inline group-focus-within:inline hover:underline",
|
|
673
|
+
node.isFocused && "inline",
|
|
674
|
+
)}
|
|
643
675
|
onClick={onOpenMarimoFile}
|
|
644
676
|
>
|
|
645
677
|
open <ExternalLinkIcon className="inline ml-1" size={12} />
|
|
646
|
-
</
|
|
678
|
+
</button>
|
|
647
679
|
)}
|
|
648
680
|
</span>
|
|
649
681
|
);
|
|
@@ -28,6 +28,7 @@ export const FileNameInput = <T extends FileInfo>({
|
|
|
28
28
|
return (
|
|
29
29
|
<input
|
|
30
30
|
ref={ref}
|
|
31
|
+
aria-label="File name"
|
|
31
32
|
className="flex-1 bg-transparent border border-border text-muted-foreground"
|
|
32
33
|
defaultValue={node.data.name}
|
|
33
34
|
onClick={(e) => e.stopPropagation()}
|
|
@@ -85,15 +85,38 @@ const RunningIcon = () => {
|
|
|
85
85
|
? "Scratchpad is running"
|
|
86
86
|
: "Jump to running cell";
|
|
87
87
|
|
|
88
|
+
const icon = (
|
|
89
|
+
<HourglassIcon className="running-app-icon" size={30} strokeWidth={1} />
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (scratchpadOnly) {
|
|
93
|
+
return (
|
|
94
|
+
<Tooltip content={tooltip} side="right">
|
|
95
|
+
<span
|
|
96
|
+
className={topLeftStatus}
|
|
97
|
+
data-testid="loading-indicator"
|
|
98
|
+
// Execution progress is advisory status, not a form result.
|
|
99
|
+
// eslint-disable-next-line jsx-a11y/prefer-tag-over-role
|
|
100
|
+
role="status"
|
|
101
|
+
aria-label={tooltip}
|
|
102
|
+
>
|
|
103
|
+
{icon}
|
|
104
|
+
</span>
|
|
105
|
+
</Tooltip>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
88
109
|
return (
|
|
89
110
|
<Tooltip content={tooltip} side="right">
|
|
90
|
-
<
|
|
91
|
-
|
|
111
|
+
<button
|
|
112
|
+
type="button"
|
|
113
|
+
aria-label={tooltip}
|
|
114
|
+
className={cn(topLeftStatus, "bg-transparent border-0 p-0")}
|
|
92
115
|
data-testid="loading-indicator"
|
|
93
|
-
onClick={
|
|
116
|
+
onClick={notebookScrollToRunning}
|
|
94
117
|
>
|
|
95
|
-
|
|
96
|
-
</
|
|
118
|
+
{icon}
|
|
119
|
+
</button>
|
|
97
120
|
</Tooltip>
|
|
98
121
|
);
|
|
99
122
|
};
|