@marimo-team/frontend 0.24.1-dev116 → 0.24.1-dev119
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{CellStatus-BnX3kFgK.js → CellStatus-LoTwk7fR.js} +1 -1
- package/dist/assets/{ConnectedDataExplorerComponent-Slfh2UI3.js → ConnectedDataExplorerComponent-DW6cJ-Lr.js} +1 -1
- package/dist/assets/{JsonOutput-i6wa-ZZg.js → JsonOutput-Bgxc9G7d.js} +2 -2
- package/dist/assets/{MarimoErrorOutput-CIYHG601.js → MarimoErrorOutput-DqpoYOvw.js} +1 -1
- package/dist/assets/{RenderHTML-BIg2a2gf.js → RenderHTML-DwhMAd9l.js} +1 -1
- package/dist/assets/{RunButton-z5x8Nygi.js → RunButton-BlpbweyH.js} +1 -1
- package/dist/assets/{VisuallyHidden-K_kroCIL.js → VisuallyHidden-CBMDmozt.js} +1 -1
- package/dist/assets/{add-cell-with-ai-P7MBEjnv.js → add-cell-with-ai-R-3DS79K.js} +1 -1
- package/dist/assets/{add-connection-dialog-B7yGNrvC.js → add-connection-dialog-BGfpw5lC.js} +1 -1
- package/dist/assets/{agent-panel-DUUWyz59.js → agent-panel-B03P2qSx.js} +1 -1
- package/dist/assets/ai-model-dropdown-DCmiuk7x.js +3 -0
- package/dist/assets/{app-config-button-vxdvoC0x.js → app-config-button-DUJC6b6x.js} +1 -1
- package/dist/assets/{blob-DDxD75AS.js → blob-B2d_dHwI.js} +1 -1
- package/dist/assets/{cell-editor-D6Cyy4UF.js → cell-editor-QmA8u1Ay.js} +1 -1
- package/dist/assets/{cell-link-B7DFi5Hf.js → cell-link-6burT2XF.js} +1 -1
- package/dist/assets/{cells-CS_eEQ19.js → cells-Cp-G34Rz.js} +1 -1
- package/dist/assets/{chat-display-CtnwRDHx.js → chat-display-Bu405iyc.js} +1 -1
- package/dist/assets/{chat-panel-DScfSsWl.js → chat-panel-BlUwNY5j.js} +1 -1
- package/dist/assets/{chat-ui-9G115u_7.js → chat-ui-DGiVDzNh.js} +1 -1
- package/dist/assets/{column-preview-BVtft2uw.js → column-preview-DHe0-7z4.js} +1 -1
- package/dist/assets/{command-palette-uGrOQhzq.js → command-palette-D-kPO0tm.js} +1 -1
- package/dist/assets/{common-CQLiJ8JV.js → common-CHR7w7aQ.js} +1 -1
- package/dist/assets/{components-B5BY9RR4.js → components-C7mWrmXr.js} +1 -1
- package/dist/assets/{components-DAhqHptJ.js → components-DUYCXngU.js} +1 -1
- package/dist/assets/{config-CJhe6_P2.js → config-B-r81uZ6.js} +1 -1
- package/dist/assets/{config-BRRSKtP3.js → config-BlNM0caL.js} +1 -1
- package/dist/assets/{datasource-Br61QFhd.js → datasource-DrODSIs0.js} +1 -1
- package/dist/assets/{dependency-graph-panel-DoAAA7Y1.js → dependency-graph-panel-AY5BEVH2.js} +1 -1
- package/dist/assets/{documentation-panel-CYUIPHeO.js → documentation-panel-CaJF7e-j.js} +1 -1
- package/dist/assets/{download-D2Ha98tp.js → download-DMxOzI7b.js} +1 -1
- package/dist/assets/{edit-page-ClGPnVOK.js → edit-page-CdCPUPqZ.js} +4 -4
- package/dist/assets/{error-entries-ooCnKQtX.js → error-entries-Cv2m1yhc.js} +1 -1
- package/dist/assets/{error-panel-CRtClD-k.js → error-panel-Bhlvt6_H.js} +1 -1
- package/dist/assets/file-explorer-panel-DMKcHVIX.js +1 -0
- package/dist/assets/{file-icons-D0QvDCq_.js → file-icons-DdGYuADJ.js} +1 -1
- package/dist/assets/file-name-input-K4MKESI_.js +3 -0
- package/dist/assets/{floating-outline-FQva8xB9.js → floating-outline-DaUHnl_e.js} +1 -1
- package/dist/assets/{gallery-page-LX7vPBcZ.js → gallery-page-C3pBpHlY.js} +1 -1
- package/dist/assets/{glide-data-editor-DgWXD84U.js → glide-data-editor-sICUMEhk.js} +1 -1
- package/dist/assets/{home-page-DtRwYsH-.js → home-page-Cir7n_EF.js} +1 -1
- package/dist/assets/index-BGT7have.css +2 -0
- package/dist/assets/{index-BphlL86Q.js → index-ClZGC-u9.js} +6 -6
- package/dist/assets/{input-C-RazPBA.js → input-DEbY281O.js} +1 -1
- package/dist/assets/{kiosk-mode-BqYH0GIF.js → kiosk-mode-mxgb5lWf.js} +1 -1
- package/dist/assets/{layout-C4U2fp5O.js → layout-CC2AHMrW.js} +3 -3
- package/dist/assets/{logs-panel-BPfZpDxG.js → logs-panel-BGS2v3ba.js} +1 -1
- package/dist/assets/{markdown-renderer-CbdXI4uO.js → markdown-renderer-BZKlbm2Q.js} +1 -1
- package/dist/assets/{mermaid-BJLFqJtJ.js → mermaid-l6lQkCib.js} +1 -1
- package/dist/assets/{name-cell-input-CObHxs3L.js → name-cell-input-BMCdK-el.js} +1 -1
- package/dist/assets/{outline-panel-Dyr_2yws.js → outline-panel-Dr5u73V6.js} +1 -1
- package/dist/assets/{packages-panel-Cato7lRU.js → packages-panel-BdpRg9tX.js} +1 -1
- package/dist/assets/{pair-with-agent-modal-bYFRKgrw.js → pair-with-agent-modal-CbUFKJHP.js} +1 -1
- package/dist/assets/{panel-accordion-state-CB4RQUbp.js → panel-accordion-state-DjUTN7i5.js} +1 -1
- package/dist/assets/{panels-CaAkyFQ2.js → panels-BsXw4GTa.js} +1 -1
- package/dist/assets/{process-output-BvjsWKqP.js → process-output-Ck7-P_xp.js} +1 -1
- package/dist/assets/{readonly-code-display-D0VodUl8.js → readonly-code-display--S2daDa6.js} +1 -1
- package/dist/assets/{readonly-python-code-DlajH1tG.js → readonly-python-code-DpIyjHqY.js} +1 -1
- package/dist/assets/{renderShortcut-DCEcfEd4.js → renderShortcut-X-CJUSM8.js} +1 -1
- package/dist/assets/{reveal-component-NBGQ6pCq.js → reveal-component-BuI7t0JO.js} +1 -1
- package/dist/assets/{run-page-TDSGBzJ2.js → run-page-DZNsqz9H.js} +1 -1
- package/dist/assets/{scratchpad-panel-smtKLHdh.js → scratchpad-panel-CARmXIqQ.js} +1 -1
- package/dist/assets/{secrets-panel-CWX3By7x.js → secrets-panel-D3BpDIgg.js} +1 -1
- package/dist/assets/{session-panel-BEpBnPuv.js → session-panel-CB5EvmZs.js} +1 -1
- package/dist/assets/{skeleton-BVafJh4G.js → skeleton-idYPIMw_.js} +1 -1
- package/dist/assets/{snippets-panel-D5Fdmjit.js → snippets-panel-D5yy7kj0.js} +1 -1
- package/dist/assets/{state-D2Q_Pzdk.js → state-B6AklCzD.js} +1 -1
- package/dist/assets/{state-CbrP-RfI.js → state-Di9TbuV9.js} +1 -1
- package/dist/assets/{switch-BzJYj8tz.js → switch-DjXr9Y9D.js} +1 -1
- package/dist/assets/{terminal-CWTEGSbX.js → terminal-CQAFGBfN.js} +1 -1
- package/dist/assets/{tracing-htk2wAUH.js → tracing-DNk3gmWv.js} +1 -1
- package/dist/assets/{tracing-panel-D_FtT8xh.js → tracing-panel-Yqazr6w_.js} +2 -2
- package/dist/assets/{tree-actions-CIKsGmOE.js → tree-actions-CG5g7aUQ.js} +1 -1
- package/dist/assets/{useAddCell-B9V9LPof.js → useAddCell-DdB_uBif.js} +1 -1
- package/dist/assets/{useBoolean-Dh6ALPsT.js → useBoolean-BXC4CItZ.js} +1 -1
- package/dist/assets/{useCellActionButton-Byz1dxEY.js → useCellActionButton-B31neI3m.js} +1 -1
- package/dist/assets/{useDebounce-Dulm61-t.js → useDebounce-DgpSQhrP.js} +1 -1
- package/dist/assets/{useDeleteCell-xIU8YxOm.js → useDeleteCell-Bz49_cFj.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-CTX4KBOX.js → useDependencyPanelTab-C_ps0r--.js} +1 -1
- package/dist/assets/{useNotebookActions-CEWtEB4v.js → useNotebookActions-Br4AVVRg.js} +1 -1
- package/dist/assets/{useRunCells-DpLEou4d.js → useRunCells-CX7oW5zy.js} +1 -1
- package/dist/assets/{useSplitCell-DIMMMQzM.js → useSplitCell-Dy2_DmJR.js} +1 -1
- package/dist/assets/{useTheme-OSyXt1jZ.js → useTheme-C-ri8BtY.js} +1 -1
- package/dist/assets/utils-D5Ywm7-q.js +1 -0
- package/dist/assets/{vega-component-D23FlAOS.js → vega-component-C7TCzyaU.js} +1 -1
- package/dist/assets/{write-secret-modal-C2V1d4p-.js → write-secret-modal-D6zoZgLY.js} +1 -1
- package/dist/index.html +31 -31
- package/package.json +1 -1
- package/src/components/ai/ai-provider-icon.tsx +2 -2
- package/src/components/ai/ai-utils.ts +1 -1
- package/src/components/app-config/ai-config.tsx +33 -37
- package/src/components/app-config/user-config-form.tsx +1 -1
- package/src/components/data-table/renderers.tsx +2 -6
- package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +832 -0
- package/src/components/editor/file-tree/__tests__/file-viewer.test.tsx +27 -0
- package/src/components/editor/file-tree/__tests__/requesting-tree.test.ts +130 -0
- package/src/components/editor/file-tree/file-browser-row.tsx +82 -0
- package/src/components/editor/file-tree/file-explorer.tsx +377 -159
- package/src/components/editor/file-tree/file-operations.tsx +1 -1
- package/src/components/editor/file-tree/file-search-results.tsx +142 -0
- package/src/components/editor/file-tree/file-tree-row.tsx +40 -0
- package/src/components/editor/file-tree/file-viewer.tsx +2 -1
- package/src/components/editor/file-tree/requesting-tree.tsx +162 -32
- package/src/components/editor/file-tree/use-file-search.ts +142 -0
- package/src/components/editor/file-tree/use-hover-expand.ts +34 -0
- package/src/components/ui/links.tsx +2 -1
- package/src/core/ai/ids/__tests__/ids.test.ts +0 -8
- package/src/core/ai/ids/ids.ts +1 -1
- package/src/core/config/config-schema.ts +1 -0
- package/src/core/network/__tests__/requests-toasting.test.tsx +14 -0
- package/src/core/network/requests-toasting.tsx +1 -1
- package/dist/assets/ai-model-dropdown-BN9nyr0_.js +0 -3
- package/dist/assets/file-explorer-panel-DB17Zw1x.js +0 -1
- package/dist/assets/file-name-input-DsaNomb7.js +0 -3
- package/dist/assets/index-D1E_LKIW.css +0 -2
- package/dist/assets/utils-B-_Wem_I.js +0 -1
|
@@ -64,6 +64,33 @@ describe("FileViewer bounded previews", () => {
|
|
|
64
64
|
store.set(requestClientAtom, null);
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
+
it("uses fetched metadata to offer opening a notebook from a search result", async () => {
|
|
68
|
+
const notebook = {
|
|
69
|
+
...file,
|
|
70
|
+
id: "/workspace/app.py",
|
|
71
|
+
path: "/workspace/app.py",
|
|
72
|
+
name: "app.py",
|
|
73
|
+
};
|
|
74
|
+
const onOpenNotebook = vi.fn();
|
|
75
|
+
const client = MockRequestClient.create({
|
|
76
|
+
sendFileDetails: vi.fn().mockResolvedValue({
|
|
77
|
+
file: { ...notebook, isMarimoFile: true },
|
|
78
|
+
contents: "import marimo",
|
|
79
|
+
mimeType: "text/plain",
|
|
80
|
+
isBase64: false,
|
|
81
|
+
isTooLarge: false,
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
store.set(requestClientAtom, client);
|
|
85
|
+
render(<FileViewer file={notebook} onOpenNotebook={onOpenNotebook} />, {
|
|
86
|
+
wrapper,
|
|
87
|
+
});
|
|
88
|
+
fireEvent.click(
|
|
89
|
+
await screen.findByRole("button", { name: "Open notebook" }),
|
|
90
|
+
);
|
|
91
|
+
expect(onOpenNotebook).toHaveBeenCalledOnce();
|
|
92
|
+
});
|
|
93
|
+
|
|
67
94
|
it("requests a bounded preview and shows oversized metadata", async () => {
|
|
68
95
|
const client = renderViewer({
|
|
69
96
|
file,
|
|
@@ -307,6 +307,109 @@ describe("RequestingTree", () => {
|
|
|
307
307
|
expect(sendListFiles).toHaveBeenCalledWith({ path: "/external" });
|
|
308
308
|
});
|
|
309
309
|
|
|
310
|
+
test("caches empty directories and shares concurrent expansion requests", async () => {
|
|
311
|
+
let finish!: (value: { files: never[] }) => void;
|
|
312
|
+
sendListFiles.mockImplementationOnce(
|
|
313
|
+
() =>
|
|
314
|
+
new Promise((resolve) => {
|
|
315
|
+
finish = resolve;
|
|
316
|
+
}),
|
|
317
|
+
);
|
|
318
|
+
const first = tree.expand(EXTERNAL_ROOT_ID);
|
|
319
|
+
const second = tree.expand(EXTERNAL_ROOT_ID);
|
|
320
|
+
expect(sendListFiles).toHaveBeenCalledOnce();
|
|
321
|
+
finish({ files: [] });
|
|
322
|
+
expect(await Promise.all([first, second])).toEqual([true, true]);
|
|
323
|
+
await tree.expand(EXTERNAL_ROOT_ID);
|
|
324
|
+
expect(sendListFiles).toHaveBeenCalledOnce();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("refreshes a cached ancestor when revealing a newly created folder", async () => {
|
|
328
|
+
await tree.expand(PRIMARY_ROOT_ID);
|
|
329
|
+
const newFolder = {
|
|
330
|
+
...PRIMARY_FILES[1],
|
|
331
|
+
id: "/root/new",
|
|
332
|
+
path: "/root/new",
|
|
333
|
+
name: "new",
|
|
334
|
+
};
|
|
335
|
+
const nested = {
|
|
336
|
+
...newFolder,
|
|
337
|
+
id: "/root/new/nested",
|
|
338
|
+
path: "/root/new/nested",
|
|
339
|
+
name: "nested",
|
|
340
|
+
};
|
|
341
|
+
sendListFiles.mockImplementation(async ({ path }: { path: string }) => {
|
|
342
|
+
if (path === "/root") {
|
|
343
|
+
return { files: [...PRIMARY_FILES, newFolder] };
|
|
344
|
+
}
|
|
345
|
+
if (path === "/root/new") {
|
|
346
|
+
return { files: [nested] };
|
|
347
|
+
}
|
|
348
|
+
return { files: [] };
|
|
349
|
+
});
|
|
350
|
+
sendListFiles.mockClear();
|
|
351
|
+
expect(
|
|
352
|
+
await tree.reveal({
|
|
353
|
+
...nested,
|
|
354
|
+
id: fileTreeNodeId("/root", nested.path),
|
|
355
|
+
rootPath: "/root",
|
|
356
|
+
isRoot: false,
|
|
357
|
+
isPrimaryRoot: true,
|
|
358
|
+
}),
|
|
359
|
+
).toBe(true);
|
|
360
|
+
expect(sendListFiles.mock.calls).toEqual([
|
|
361
|
+
[{ path: "/root" }],
|
|
362
|
+
[{ path: "/root/new" }],
|
|
363
|
+
[{ path: "/root/new/nested" }],
|
|
364
|
+
]);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test("reports a search folder that no longer exists after refreshing its parent", async () => {
|
|
368
|
+
await tree.expand(PRIMARY_ROOT_ID);
|
|
369
|
+
expect(
|
|
370
|
+
await tree.reveal({
|
|
371
|
+
...PRIMARY_FILES[1],
|
|
372
|
+
id: fileTreeNodeId("/root", "/root/missing"),
|
|
373
|
+
path: "/root/missing",
|
|
374
|
+
rootPath: "/root",
|
|
375
|
+
isRoot: false,
|
|
376
|
+
isPrimaryRoot: true,
|
|
377
|
+
}),
|
|
378
|
+
).toBe(false);
|
|
379
|
+
expect(toast).toHaveBeenCalledWith(
|
|
380
|
+
expect.objectContaining({ title: "Could not reveal folder" }),
|
|
381
|
+
);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test("retries a failed expansion", async () => {
|
|
385
|
+
sendListFiles.mockRejectedValueOnce(new Error("Offline"));
|
|
386
|
+
expect(await tree.expand(EXTERNAL_ROOT_ID)).toBe(false);
|
|
387
|
+
expect(await tree.expand(EXTERNAL_ROOT_ID)).toBe(true);
|
|
388
|
+
expect(sendListFiles).toHaveBeenCalledTimes(2);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test("preserves loaded descendants when refreshing their parent", async () => {
|
|
392
|
+
await tree.expand(PRIMARY_ROOT_ID);
|
|
393
|
+
const folderId = fileTreeNodeId("/root", "/root/folder1");
|
|
394
|
+
await tree.expand(folderId);
|
|
395
|
+
await tree.refreshPath("/root" as FilePath);
|
|
396
|
+
sendListFiles.mockClear();
|
|
397
|
+
await tree.expand(folderId);
|
|
398
|
+
expect(sendListFiles).not.toHaveBeenCalled();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("reloads a collapsed cached folder after refreshing", async () => {
|
|
402
|
+
await tree.expand(PRIMARY_ROOT_ID);
|
|
403
|
+
const folderId = fileTreeNodeId("/root", "/root/folder1");
|
|
404
|
+
await tree.expand(folderId);
|
|
405
|
+
await tree.refreshAll([]);
|
|
406
|
+
sendListFiles.mockClear();
|
|
407
|
+
await tree.expand(folderId);
|
|
408
|
+
expect(sendListFiles).toHaveBeenCalledExactlyOnceWith({
|
|
409
|
+
path: "/root/folder1",
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
310
413
|
test("refreshes supplied open folders in addition to configured roots", async () => {
|
|
311
414
|
await tree.expand(PRIMARY_ROOT_ID);
|
|
312
415
|
sendListFiles.mockClear();
|
|
@@ -318,6 +421,33 @@ describe("RequestingTree", () => {
|
|
|
318
421
|
expect(sendListFiles).toHaveBeenCalledWith({ path: "/root/folder1" });
|
|
319
422
|
});
|
|
320
423
|
|
|
424
|
+
test("invalidates collapsed copies independently for overlapping roots", async () => {
|
|
425
|
+
getRoots.mockResolvedValue({
|
|
426
|
+
roots: [
|
|
427
|
+
{ path: "/root", name: "workspace", isPrimary: true },
|
|
428
|
+
{ path: "/root/folder1", name: "nested root", isPrimary: false },
|
|
429
|
+
],
|
|
430
|
+
});
|
|
431
|
+
const overlapping = new RequestingTree({
|
|
432
|
+
getRoots,
|
|
433
|
+
listFiles: sendListFiles,
|
|
434
|
+
createFileOrFolder: sendCreateFileOrFolder,
|
|
435
|
+
deleteFileOrFolder: sendDeleteFileOrFolder,
|
|
436
|
+
copyFileOrFolder: sendCopyFileOrFolder,
|
|
437
|
+
renameFileOrFolder: sendRenameFileOrFolder,
|
|
438
|
+
});
|
|
439
|
+
await overlapping.initialize(onChange);
|
|
440
|
+
await overlapping.expand(PRIMARY_ROOT_ID);
|
|
441
|
+
const collapsedCopy = fileTreeNodeId("/root", "/root/folder1");
|
|
442
|
+
await overlapping.expand(collapsedCopy);
|
|
443
|
+
await overlapping.refreshAll([]);
|
|
444
|
+
sendListFiles.mockClear();
|
|
445
|
+
await overlapping.expand(collapsedCopy);
|
|
446
|
+
expect(sendListFiles).toHaveBeenCalledExactlyOnceWith({
|
|
447
|
+
path: "/root/folder1",
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
321
451
|
test("keeps existing children when a refresh fails", async () => {
|
|
322
452
|
await tree.expand(PRIMARY_ROOT_ID);
|
|
323
453
|
const beforeRefresh = onChange.mock.calls.at(-1)?.[0];
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import type { RowRendererProps } from "react-arborist";
|
|
5
|
+
import { useDrop } from "react-dnd";
|
|
6
|
+
import { FileTreeRow } from "./file-tree-row";
|
|
7
|
+
import { useHoverExpand } from "./use-hover-expand";
|
|
8
|
+
import type { FileTreeNode } from "./requesting-tree";
|
|
9
|
+
|
|
10
|
+
export function FileBrowserRow(props: RowRendererProps<FileTreeNode>) {
|
|
11
|
+
return props.node.data.isDirectory ? (
|
|
12
|
+
<FolderDropRow {...props} />
|
|
13
|
+
) : (
|
|
14
|
+
<FileTreeRow {...props} />
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function FolderDropRow(props: RowRendererProps<FileTreeNode>) {
|
|
19
|
+
const { node } = props;
|
|
20
|
+
const tree = node.tree;
|
|
21
|
+
const rowRef = useRef<HTMLDivElement | null>(null);
|
|
22
|
+
const [{ isHovering }, dropRef] = useDrop(
|
|
23
|
+
() => ({
|
|
24
|
+
// Arborist's drag source uses NODE. Keep its validation and move handler,
|
|
25
|
+
// but treat the entire folder row as a destination, without reorder zones.
|
|
26
|
+
accept: "NODE",
|
|
27
|
+
canDrop: () => tree.canDrop(),
|
|
28
|
+
collect: (monitor) => ({
|
|
29
|
+
isHovering: monitor.isOver({ shallow: true }) && monitor.canDrop(),
|
|
30
|
+
}),
|
|
31
|
+
hover: () => {
|
|
32
|
+
if (
|
|
33
|
+
tree.state.dnd.parentId !== node.id ||
|
|
34
|
+
tree.state.dnd.index !== null
|
|
35
|
+
) {
|
|
36
|
+
tree.dispatch({
|
|
37
|
+
type: "DND_HOVERING",
|
|
38
|
+
parentId: node.id,
|
|
39
|
+
index: null,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (!tree.canDrop()) {
|
|
43
|
+
tree.hideCursor();
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
drop: () => {
|
|
47
|
+
if (!tree.canDrop()) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
void tree.props.onMove?.({
|
|
51
|
+
dragIds: tree.state.dnd.dragIds,
|
|
52
|
+
dragNodes: tree.dragNodes,
|
|
53
|
+
parentId: node.id,
|
|
54
|
+
parentNode: node,
|
|
55
|
+
index: 0,
|
|
56
|
+
});
|
|
57
|
+
tree.open(node.id);
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
[tree, node.id],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
useHoverExpand(node, isHovering);
|
|
64
|
+
|
|
65
|
+
// Replacing innerRef avoids attaching Arborist's positional drop target.
|
|
66
|
+
// Retain its focus behavior for keyboard navigation and rename completion.
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (node.isFocused && !node.isEditing) {
|
|
69
|
+
rowRef.current?.focus({ preventScroll: true });
|
|
70
|
+
}
|
|
71
|
+
}, [node.isFocused, node.isEditing]);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<FileTreeRow
|
|
75
|
+
{...props}
|
|
76
|
+
innerRef={(element) => {
|
|
77
|
+
rowRef.current = element;
|
|
78
|
+
dropRef(element);
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|