@marimo-team/frontend 0.24.1-dev99 → 0.24.2-dev129

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/dist/assets/{CellStatus-wvvZkEPY.js → CellStatus-LoTwk7fR.js} +1 -1
  2. package/dist/assets/{ConnectedDataExplorerComponent-Dkqk66Mv.js → ConnectedDataExplorerComponent-DW6cJ-Lr.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-npvnLp9x.js → ErrorBoundary-CDVGFYfr.js} +1 -1
  4. package/dist/assets/JsonOutput-CzIGPEZQ.js +50 -0
  5. package/dist/assets/{LazyAnyLanguageCodeMirror-zSwE3dvU.js → LazyAnyLanguageCodeMirror-LboTfNDv.js} +2 -2
  6. package/dist/assets/{MarimoErrorOutput-CeUo5oka.js → MarimoErrorOutput-DqpoYOvw.js} +1 -1
  7. package/dist/assets/RenderHTML-DwhMAd9l.js +1 -0
  8. package/dist/assets/{RunButton-BaV8A-z4.js → RunButton-BlpbweyH.js} +1 -1
  9. package/dist/assets/{VisuallyHidden-SChAyKIQ.js → VisuallyHidden-CBMDmozt.js} +1 -1
  10. package/dist/assets/{add-cell-with-ai-DoLkfzYn.js → add-cell-with-ai-R-3DS79K.js} +1 -1
  11. package/dist/assets/{add-connection-dialog-C1IF1nR0.js → add-connection-dialog-BGfpw5lC.js} +1 -1
  12. package/dist/assets/{agent-panel-CG154QrU.js → agent-panel-Cgt_K0tD.js} +1 -1
  13. package/dist/assets/ai-model-dropdown-DCmiuk7x.js +3 -0
  14. package/dist/assets/alert-dialog-CA-2mBs3.js +11 -0
  15. package/dist/assets/any-language-editor-DflLjPSQ.js +1 -0
  16. package/dist/assets/{app-config-button-ktBH1pEB.js → app-config-button-DUJC6b6x.js} +1 -1
  17. package/dist/assets/badge-BSgtkC2e.js +1 -0
  18. package/dist/assets/{blob-DVtvZmWH.js → blob-B2d_dHwI.js} +1 -1
  19. package/dist/assets/button-Dh8EwhsF.js +1 -0
  20. package/dist/assets/{cache-panel-C7jsiqCa.js → cache-panel-DqgtC98s.js} +1 -1
  21. package/dist/assets/{capabilities-LWzb3E3M.js → capabilities-D8kPgMKr.js} +1 -1
  22. package/dist/assets/{cell-editor-DoDqaCZp.js → cell-editor-Dw09J9yD.js} +6 -6
  23. package/dist/assets/{cell-link-Dqqt6Rgs.js → cell-link-6burT2XF.js} +1 -1
  24. package/dist/assets/{cells-D4RwofRx.js → cells-Cp-G34Rz.js} +56 -56
  25. package/dist/assets/{chat-display-COmzHgx5.js → chat-display-Bu405iyc.js} +1 -1
  26. package/dist/assets/{chat-panel-B2tyEfGW.js → chat-panel-BlUwNY5j.js} +1 -1
  27. package/dist/assets/{chat-ui-B5bsr-7V.js → chat-ui-DGiVDzNh.js} +1 -1
  28. package/dist/assets/{circle--ysFeW9z.js → circle-ByJdIf5p.js} +1 -1
  29. package/dist/assets/{column-preview-CW5DPFvy.js → column-preview-riSXbYe_.js} +1 -1
  30. package/dist/assets/{command-palette-B9dyvMvJ.js → command-palette-CCVzJcyf.js} +1 -1
  31. package/dist/assets/{common-CC3tuCpi.js → common-CHR7w7aQ.js} +1 -1
  32. package/dist/assets/{components-D02ZZZMP.js → components-C7mWrmXr.js} +1 -1
  33. package/dist/assets/{components-CBlGIIAX.js → components-DUYCXngU.js} +1 -1
  34. package/dist/assets/config-B-r81uZ6.js +1 -0
  35. package/dist/assets/{config-D4frVGQ4.js → config-BlNM0caL.js} +1 -1
  36. package/dist/assets/{copy-Cg05mM3C.js → copy-DlEomWzI.js} +1 -1
  37. package/dist/assets/{copy-icon-CRxVZTJ9.js → copy-icon-z8fuTE3w.js} +1 -1
  38. package/dist/assets/{createReducer-Bq-nQJiK.js → createReducer-B_XmHROl.js} +1 -1
  39. package/dist/assets/{datasource-Do8f2hFf.js → datasource-DrODSIs0.js} +1 -1
  40. package/dist/assets/{dates-DK5JikJo.js → dates-B58T85Jq.js} +1 -1
  41. package/dist/assets/dependency-graph-panel-AY5BEVH2.js +5 -0
  42. package/dist/assets/{dist-7qdHLg-L.js → dist-B0RXffUm.js} +1 -1
  43. package/dist/assets/dist-B0qYy1Ye.js +1 -0
  44. package/dist/assets/{dist-B97dKQGP.js → dist-B8pdsUrS.js} +1 -1
  45. package/dist/assets/{dist-BcfF-rkl.js → dist-BF7Lrekv.js} +1 -1
  46. package/dist/assets/dist-BL8RjfvI.js +1 -0
  47. package/dist/assets/dist-BVZxge7j.js +1 -0
  48. package/dist/assets/{dist-fxnYzq-R.js → dist-BaRNSaca.js} +1 -1
  49. package/dist/assets/dist-Bk2yQtw4.js +1 -0
  50. package/dist/assets/dist-BsfiiKoy.js +1 -0
  51. package/dist/assets/dist-BskmVd3B.js +1 -0
  52. package/dist/assets/{dist-DL1Thauf.js → dist-Bsu3h9O7.js} +1 -1
  53. package/dist/assets/{dist-PIe1xcZo.js → dist-BtC9ym6k.js} +7 -7
  54. package/dist/assets/{dist-DeuWxYVk.js → dist-Bv0S1RlZ.js} +1 -1
  55. package/dist/assets/{dist-CidBzUqR.js → dist-C--ZICxQ.js} +1 -1
  56. package/dist/assets/dist-C-2fD3XY.js +1 -0
  57. package/dist/assets/{dist-DzAWvPVK.js → dist-CKUpxYGQ.js} +1 -1
  58. package/dist/assets/dist-CMFMfBm1.js +1 -0
  59. package/dist/assets/dist-CZNULd6Z.js +2 -0
  60. package/dist/assets/dist-CjFduPiC.js +1 -0
  61. package/dist/assets/{dist-DoAjHgMj.js → dist-CmImLjus.js} +1 -1
  62. package/dist/assets/{dist-BKx0MLbb.js → dist-Cq8NOszX.js} +1 -1
  63. package/dist/assets/{dist-DCc0VfiO.js → dist-Cx4BE7jC.js} +3 -3
  64. package/dist/assets/dist-D45ScydS.js +1 -0
  65. package/dist/assets/dist-D6dg87r7.js +1 -0
  66. package/dist/assets/dist-DEjfQLz0.js +1 -0
  67. package/dist/assets/{dist-BcYW-EJk.js → dist-DJOWOALB.js} +1 -1
  68. package/dist/assets/{dist-CxxZaSme.js → dist-DPoYlxSC.js} +1 -1
  69. package/dist/assets/dist-DR-Y-nl3.js +1 -0
  70. package/dist/assets/dist-Dahlp9JQ.js +1 -0
  71. package/dist/assets/{dist-DIkuvChK.js → dist-Db-HC93w.js} +1 -1
  72. package/dist/assets/{dist-DTM7BAUu.js → dist-DdOlZ6mj.js} +1 -1
  73. package/dist/assets/{dist-Dz_04liz.js → dist-DiYjQAhI.js} +1 -1
  74. package/dist/assets/{dist-BO5c5n_b.js → dist-DkPPv0FB.js} +1 -1
  75. package/dist/assets/{dist-DkCO0b-0.js → dist-DmGtPaVM.js} +1 -1
  76. package/dist/assets/{dist-CXmhxecl.js → dist-DnxYYyU7.js} +1 -1
  77. package/dist/assets/{dist-CO1Hfv5H.js → dist-Dw9YRsCU.js} +1 -1
  78. package/dist/assets/dist-Dwx3KdUP.js +1 -0
  79. package/dist/assets/{dist-BgL43w5r.js → dist-DzCQtGkt.js} +1 -1
  80. package/dist/assets/dist-JweaDt6P.js +1 -0
  81. package/dist/assets/{dist-Dbu5sPgR.js → dist-gELFCfHH.js} +1 -1
  82. package/dist/assets/dist-kGDJ0AWF.js +1 -0
  83. package/dist/assets/{dist-Cqm1W-Zm.js → dist-kaLvV_d4.js} +1 -1
  84. package/dist/assets/{dist-CEeRVpMD.js → dist-qvk3g06H.js} +1 -1
  85. package/dist/assets/dist-wCw2twuB.js +1 -0
  86. package/dist/assets/dist-wF42tUcr.js +1 -0
  87. package/dist/assets/dist-wY2yia-1.js +3 -0
  88. package/dist/assets/{documentation-panel-DvHjxlli.js → documentation-panel-CaJF7e-j.js} +1 -1
  89. package/dist/assets/{download-BP8MrprC.js → download-DMxOzI7b.js} +1 -1
  90. package/dist/assets/dropdown-menu-C40jM9o4.js +1 -0
  91. package/dist/assets/{edit-page-71cK-Nhe.js → edit-page-Vqd9NedM.js} +6 -6
  92. package/dist/assets/{error-banner-BYN8YUMe.js → error-banner-Dw5YY9wq.js} +1 -1
  93. package/dist/assets/{error-entries-DOZ1u97X.js → error-entries-Cv2m1yhc.js} +1 -1
  94. package/dist/assets/{error-panel-CINcf76Z.js → error-panel-Bhlvt6_H.js} +1 -1
  95. package/dist/assets/{es-BuMFDpR4.js → es-CrFhA96G.js} +1 -1
  96. package/dist/assets/{esm-Dw0vvSg2.js → esm-BHMOvJzM.js} +1 -1
  97. package/dist/assets/{esm-BGpfQNhD.js → esm-DmqQGMel.js} +1 -1
  98. package/dist/assets/file-explorer-panel-6HvNfCrJ.js +1 -0
  99. package/dist/assets/{file-icons-DgKj9mrV.js → file-icons-DdGYuADJ.js} +1 -1
  100. package/dist/assets/file-name-input-K4MKESI_.js +3 -0
  101. package/dist/assets/{floating-outline-CRXIsrPm.js → floating-outline-DaUHnl_e.js} +1 -1
  102. package/dist/assets/{formats-DSzGCKeO.js → formats-C6s96nu2.js} +1 -1
  103. package/dist/assets/{formatting-BJlT1s_3.js → formatting-BWZH2nBg.js} +1 -1
  104. package/dist/assets/{fullscreen-0EEPR7uc.js → fullscreen-BH2jz-F4.js} +5 -5
  105. package/dist/assets/{gallery-page-TqnFYECu.js → gallery-page-C3pBpHlY.js} +1 -1
  106. package/dist/assets/{glide-data-editor-DcY3nXI8.js → glide-data-editor-sICUMEhk.js} +1 -1
  107. package/dist/assets/{glide-utils-CR1sWLZU.js → glide-utils-UGsrRHYj.js} +1 -1
  108. package/dist/assets/home-page-BLd6rnbk.js +4 -0
  109. package/dist/assets/{html-to-image-DMn_7CCA.js → html-to-image-D6GUC5q6.js} +1 -1
  110. package/dist/assets/index-BGT7have.css +2 -0
  111. package/dist/assets/index-ipFYAeCF.js +29 -0
  112. package/dist/assets/{input-Daz6sk8l.js → input-DEbY281O.js} +2 -2
  113. package/dist/assets/{kiosk-mode-CzGt3mc2.js → kiosk-mode-mxgb5lWf.js} +1 -1
  114. package/dist/assets/{layout-KJMQADEj.js → layout-DWnrxIo2.js} +5 -5
  115. package/dist/assets/links-DLvOXyO_.js +1 -0
  116. package/dist/assets/{logs-panel-DFibPeCT.js → logs-panel-BGS2v3ba.js} +1 -1
  117. package/dist/assets/lua-1tNpM92n.js +1 -0
  118. package/dist/assets/{lua-CRIS5afH.js → lua-DXBxhmso.js} +1 -1
  119. package/dist/assets/{markdown-renderer-CdZX2MvM.js → markdown-renderer-BZKlbm2Q.js} +1 -1
  120. package/dist/assets/menu-items-BSRA2YI9.js +1 -0
  121. package/dist/assets/{mermaid-CeseGGnS.js → mermaid-l6lQkCib.js} +1 -1
  122. package/dist/assets/{name-cell-input-B-d5Jwh4.js → name-cell-input-BMCdK-el.js} +1 -1
  123. package/dist/assets/{numbers-DKurV_vq.js → numbers-Bc-r3jsC.js} +1 -1
  124. package/dist/assets/{outline-panel-D2Wk10hY.js → outline-panel-Dr5u73V6.js} +1 -1
  125. package/dist/assets/{packages-panel-CQsbxIlG.js → packages-panel-BdpRg9tX.js} +1 -1
  126. package/dist/assets/{pair-with-agent-modal-CNxmIMFM.js → pair-with-agent-modal-CbUFKJHP.js} +1 -1
  127. package/dist/assets/{panel-accordion-state-D6-FMXE2.js → panel-accordion-state-DjUTN7i5.js} +1 -1
  128. package/dist/assets/{panels-BgcK_4M7.js → panels-CWn0Yh1D.js} +1 -1
  129. package/dist/assets/process-output-Ck7-P_xp.js +1 -0
  130. package/dist/assets/{readonly-code-display-BvqLeUsJ.js → readonly-code-display--S2daDa6.js} +1 -1
  131. package/dist/assets/{readonly-python-code-GSN4D4sb.js → readonly-python-code-DpIyjHqY.js} +1 -1
  132. package/dist/assets/{renderShortcut-DK_OucLT.js → renderShortcut-X-CJUSM8.js} +1 -1
  133. package/dist/assets/{reveal-component-u9ED2diI.js → reveal-component-nZIAapoD.js} +1 -1
  134. package/dist/assets/{run-page-U5iuhyz8.js → run-page-9pl8qhQR.js} +1 -1
  135. package/dist/assets/{runs-BQTlv6Ja.js → runs-euHSzlw_.js} +1 -1
  136. package/dist/assets/{scratchpad-panel--YOR0kUB.js → scratchpad-panel-BleGt2KM.js} +1 -1
  137. package/dist/assets/{scroll-to-bottom-button-xua0e9rA.js → scroll-to-bottom-button-DufiYUD7.js} +1 -1
  138. package/dist/assets/{secrets-panel-CZPbFAZH.js → secrets-panel-D3BpDIgg.js} +1 -1
  139. package/dist/assets/select-nLt2y0Sw.js +1 -0
  140. package/dist/assets/{session-CEdta5_Q.js → session-Sz5nSFku.js} +1 -1
  141. package/dist/assets/session-panel-DRGPlL5J.js +1 -0
  142. package/dist/assets/{share-C2MPy_VX.js → share-CPtBiNgr.js} +1 -1
  143. package/dist/assets/{skeleton-CsceCAjw.js → skeleton-BKeh8hf3.js} +1 -1
  144. package/dist/assets/{snippets-panel-BxwHsPmW.js → snippets-panel-D5yy7kj0.js} +1 -1
  145. package/dist/assets/{spec-CUFquO6R.js → spec-DKsAmLTi.js} +1 -1
  146. package/dist/assets/{state-BqrF--O0.js → state-B6AklCzD.js} +1 -1
  147. package/dist/assets/{state-C2c3_VuY.js → state-B8XRqyFg.js} +1 -1
  148. package/dist/assets/{state-CHplO0zn.js → state-Di9TbuV9.js} +1 -1
  149. package/dist/assets/{strings-CpYReX2Q.js → strings-CReV1AYG.js} +1 -1
  150. package/dist/assets/{swiper-component-Bot7ehYH.js → swiper-component-5Kdi1-Gn.js} +1 -1
  151. package/dist/assets/{switch-CtH5Ct94.js → switch-DjXr9Y9D.js} +1 -1
  152. package/dist/assets/{tabs-c6mpccpH.js → tabs-Cy1qXLzH.js} +1 -1
  153. package/dist/assets/{terminal-C73iz1fU.js → terminal-CQAFGBfN.js} +1 -1
  154. package/dist/assets/{tooltip-Cwltdez8.js → tooltip-BG35uS_N.js} +1 -1
  155. package/dist/assets/{tooltip-D7dq6bnc.js → tooltip-BUtPzKB4.js} +1 -1
  156. package/dist/assets/{tracing-Dhq121dR.js → tracing-DNk3gmWv.js} +1 -1
  157. package/dist/assets/{tracing-panel-IKt1933_.js → tracing-panel-Yqazr6w_.js} +2 -2
  158. package/dist/assets/{tree-actions-DIfWonCA.js → tree-actions-CG5g7aUQ.js} +1 -1
  159. package/dist/assets/{useAddCell-BYrVaYfZ.js → useAddCell-DdB_uBif.js} +1 -1
  160. package/dist/assets/{useBoolean-ZU5StAbr.js → useBoolean-BXC4CItZ.js} +1 -1
  161. package/dist/assets/{useCellActionButton-DbXDs0MA.js → useCellActionButton-B31neI3m.js} +1 -1
  162. package/dist/assets/{useDebounce-Dulm61-t.js → useDebounce-DgpSQhrP.js} +1 -1
  163. package/dist/assets/{useDeleteCell-Wgcl3CRG.js → useDeleteCell-Bz49_cFj.js} +1 -1
  164. package/dist/assets/{useDependencyPanelTab-C5AU8GKL.js → useDependencyPanelTab-C_ps0r--.js} +1 -1
  165. package/dist/assets/{useEventListener-D27Dii_l.js → useEventListener-92uWqz-P.js} +1 -1
  166. package/dist/assets/{useIframeCapabilities-DEpTCh1Q.js → useIframeCapabilities-C7Di8f2y.js} +1 -1
  167. package/dist/assets/{useInstallPackage-u7d0wo79.js → useInstallPackage-DsD08SLK.js} +1 -1
  168. package/dist/assets/{useInterval-B1_J4wdT.js → useInterval-B4v7_zd0.js} +1 -1
  169. package/dist/assets/{useNotebookActions-FuMERtHq.js → useNotebookActions-CQtbk9CB.js} +2 -2
  170. package/dist/assets/{useRunCells-DTdMRbkE.js → useRunCells-CX7oW5zy.js} +1 -1
  171. package/dist/assets/{useSplitCell-2z0zEB8X.js → useSplitCell-Dy2_DmJR.js} +1 -1
  172. package/dist/assets/{useTheme-CF-zG9YH.js → useTheme-C-ri8BtY.js} +1 -1
  173. package/dist/assets/utils-D5Ywm7-q.js +1 -0
  174. package/dist/assets/{vega-component-BSXJcTJb.js → vega-component-C7TCzyaU.js} +1 -1
  175. package/dist/assets/write-secret-modal-D6zoZgLY.js +2 -0
  176. package/dist/index.html +73 -73
  177. package/package.json +26 -26
  178. package/src/components/ai/ai-provider-icon.tsx +2 -2
  179. package/src/components/ai/ai-utils.ts +1 -1
  180. package/src/components/app-config/ai-config.tsx +33 -37
  181. package/src/components/app-config/user-config-form.tsx +1 -1
  182. package/src/components/data-table/__tests__/column-explorer.test.tsx +64 -4
  183. package/src/components/data-table/__tests__/column-visibility-dropdown.test.tsx +137 -4
  184. package/src/components/data-table/__tests__/url-detector.test.tsx +1 -1
  185. package/src/components/data-table/__tests__/useColumnVisibility.test.ts +153 -1
  186. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +54 -25
  187. package/src/components/data-table/column-visibility-dropdown.tsx +62 -14
  188. package/src/components/data-table/date-popover.tsx +2 -1
  189. package/src/components/data-table/hooks/use-column-visibility.ts +76 -0
  190. package/src/components/data-table/renderers.tsx +4 -8
  191. package/src/components/data-table/show-only-column-button.tsx +58 -0
  192. package/src/components/dependency-graph/__tests__/elements.test.ts +151 -0
  193. package/src/components/dependency-graph/custom-edge.tsx +68 -0
  194. package/src/components/dependency-graph/custom-node.tsx +54 -26
  195. package/src/components/dependency-graph/dependency-graph-tree.tsx +110 -21
  196. package/src/components/dependency-graph/elements.ts +225 -31
  197. package/src/components/dependency-graph/panels.tsx +23 -2
  198. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +78 -0
  199. package/src/components/dependency-graph/utils/layout.ts +108 -18
  200. package/src/components/editor/Output.tsx +40 -0
  201. package/src/components/editor/__tests__/Output.test.tsx +85 -2
  202. package/src/components/editor/connections/secret-combobox.tsx +3 -1
  203. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +832 -0
  204. package/src/components/editor/file-tree/__tests__/file-viewer.test.tsx +27 -0
  205. package/src/components/editor/file-tree/__tests__/requesting-tree.test.ts +130 -0
  206. package/src/components/editor/file-tree/file-browser-row.tsx +82 -0
  207. package/src/components/editor/file-tree/file-explorer.tsx +377 -159
  208. package/src/components/editor/file-tree/file-operations.tsx +1 -1
  209. package/src/components/editor/file-tree/file-search-results.tsx +142 -0
  210. package/src/components/editor/file-tree/file-tree-row.tsx +40 -0
  211. package/src/components/editor/file-tree/file-viewer.tsx +2 -1
  212. package/src/components/editor/file-tree/renderers.tsx +4 -2
  213. package/src/components/editor/file-tree/requesting-tree.tsx +162 -32
  214. package/src/components/editor/file-tree/use-file-search.ts +142 -0
  215. package/src/components/editor/file-tree/use-hover-expand.ts +34 -0
  216. package/src/components/editor/navigation/__tests__/navigation.test.ts +30 -0
  217. package/src/components/editor/navigation/navigation.ts +20 -0
  218. package/src/components/forms/form.tsx +6 -4
  219. package/src/components/home/components.tsx +1 -0
  220. package/src/components/static-html/share-modal.tsx +3 -2
  221. package/src/components/ui/__tests__/badge.test.ts +63 -0
  222. package/src/components/ui/__tests__/button.test.ts +19 -0
  223. package/src/components/ui/__tests__/fullscreen.test.tsx +88 -0
  224. package/src/components/ui/__tests__/menu-items.test.ts +31 -0
  225. package/src/components/ui/badge.tsx +9 -7
  226. package/src/components/ui/button.tsx +1 -0
  227. package/src/components/ui/combobox.tsx +1 -1
  228. package/src/components/ui/date-input.tsx +1 -1
  229. package/src/components/ui/dialog.tsx +3 -1
  230. package/src/components/ui/fullscreen.tsx +43 -1
  231. package/src/components/ui/input.tsx +5 -1
  232. package/src/components/ui/links.tsx +2 -1
  233. package/src/components/ui/menu-items.tsx +9 -9
  234. package/src/components/ui/native-select.tsx +2 -2
  235. package/src/components/ui/navigation.tsx +1 -1
  236. package/src/components/ui/range-slider.tsx +5 -3
  237. package/src/components/ui/select.tsx +1 -1
  238. package/src/components/ui/sheet.tsx +1 -1
  239. package/src/components/ui/toast.tsx +8 -5
  240. package/src/components/ui/toaster.tsx +2 -2
  241. package/src/core/ai/ids/__tests__/ids.test.ts +0 -8
  242. package/src/core/ai/ids/ids.ts +1 -1
  243. package/src/core/cells/__tests__/add-missing-import.test.ts +20 -0
  244. package/src/core/codemirror/language/__tests__/utils.test.ts +1 -1
  245. package/src/core/config/config-schema.ts +1 -0
  246. package/src/core/hotkeys/hotkeys.ts +5 -0
  247. package/src/core/network/__tests__/requests-toasting.test.tsx +14 -0
  248. package/src/core/network/requests-toasting.tsx +1 -1
  249. package/src/css/globals.css +28 -0
  250. package/src/hooks/useEventListener.ts +1 -1
  251. package/src/plugins/core/RenderHTML.tsx +6 -4
  252. package/src/plugins/impl/MatrixPlugin.tsx +1 -1
  253. package/src/plugins/impl/code/any-language-editor.tsx +7 -4
  254. package/src/plugins/impl/data-frames/__tests__/schema.test.ts +34 -1
  255. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +25 -25
  256. package/src/plugins/impl/data-frames/schema.ts +5 -11
  257. package/src/plugins/impl/panel/__tests__/utils.test.ts +4 -4
  258. package/src/plugins/impl/vega/__tests__/vega.test.ts +1 -1
  259. package/src/stories/cell.stories.tsx +15 -11
  260. package/src/utils/__tests__/events.test.ts +1 -1
  261. package/dist/assets/JsonOutput-CRLQQVKf.js +0 -50
  262. package/dist/assets/RenderHTML-YkwVPe9V.js +0 -1
  263. package/dist/assets/ai-model-dropdown-CrSrb8Zz.js +0 -3
  264. package/dist/assets/alert-dialog-BVi_GyOB.js +0 -11
  265. package/dist/assets/any-language-editor-BL8H-DwB.js +0 -1
  266. package/dist/assets/badge-D_-uqy2N.js +0 -1
  267. package/dist/assets/button-Btj6GXNy.js +0 -1
  268. package/dist/assets/config-ycxJQP2b.js +0 -1
  269. package/dist/assets/dependency-graph-panel-Dg-tUB9T.js +0 -5
  270. package/dist/assets/dist-2oRmY-BC.js +0 -1
  271. package/dist/assets/dist-8lhGoJ54.js +0 -3
  272. package/dist/assets/dist-B4QaGCvF.js +0 -1
  273. package/dist/assets/dist-B_ABJVTW.js +0 -1
  274. package/dist/assets/dist-Bke0Ybxy.js +0 -1
  275. package/dist/assets/dist-Bw8CAY5i.js +0 -1
  276. package/dist/assets/dist-BwmO3SSk.js +0 -1
  277. package/dist/assets/dist-C3KxgR0m.js +0 -1
  278. package/dist/assets/dist-CLE562nZ.js +0 -1
  279. package/dist/assets/dist-CLi0j3bw.js +0 -1
  280. package/dist/assets/dist-CMIyXejt.js +0 -1
  281. package/dist/assets/dist-CbQrIaPV.js +0 -2
  282. package/dist/assets/dist-CpgfIIzU.js +0 -1
  283. package/dist/assets/dist-D7aYPqs9.js +0 -1
  284. package/dist/assets/dist-DKCF5R0T.js +0 -1
  285. package/dist/assets/dist-DbFa9cik.js +0 -1
  286. package/dist/assets/dist-DdPuhs_-.js +0 -1
  287. package/dist/assets/dist-DkfdZExz.js +0 -1
  288. package/dist/assets/dist-hOh6JBwi.js +0 -1
  289. package/dist/assets/dist-mY1j_5eA.js +0 -1
  290. package/dist/assets/dist-tg3aVnpD.js +0 -1
  291. package/dist/assets/dropdown-menu-CqNabM1f.js +0 -1
  292. package/dist/assets/file-explorer-panel-cflxW8a7.js +0 -1
  293. package/dist/assets/file-name-input-CQF4Ovwx.js +0 -3
  294. package/dist/assets/home-page-DPRHPAQM.js +0 -4
  295. package/dist/assets/index-BkIXRAbi.css +0 -2
  296. package/dist/assets/index-p49fbIlR.js +0 -29
  297. package/dist/assets/links-DPh7yoP-.js +0 -1
  298. package/dist/assets/lua-_4wuYo1j.js +0 -1
  299. package/dist/assets/menu-items-BSVrMleU.js +0 -1
  300. package/dist/assets/process-output-DvTtgoMs.js +0 -1
  301. package/dist/assets/select-czJLm6jf.js +0 -1
  302. package/dist/assets/session-panel-C_N3WGWQ.js +0 -1
  303. package/dist/assets/utils-CWSZQEjr.js +0 -1
  304. package/dist/assets/write-secret-modal-lNU_7c2x.js +0 -2
@@ -16,14 +16,17 @@ import {
16
16
  PlaySquareIcon,
17
17
  UploadIcon,
18
18
  ViewIcon,
19
+ XIcon,
19
20
  } from "lucide-react";
20
21
  import React, { Suspense, use, useRef, useState } from "react";
21
22
  import {
23
+ type DragPreviewProps,
22
24
  type NodeApi,
23
25
  type NodeRendererProps,
24
26
  Tree,
25
27
  type TreeApi,
26
28
  } from "react-arborist";
29
+ import { createPortal } from "react-dom";
27
30
  import useEvent from "react-use-event-hook";
28
31
  import {
29
32
  FILE_ICON,
@@ -54,6 +57,7 @@ import {
54
57
  DropdownMenuSeparator,
55
58
  } from "@/components/ui/dropdown-menu";
56
59
  import { Tooltip } from "@/components/ui/tooltip";
60
+ import { SearchInput } from "@/components/ui/input";
57
61
  import { toast } from "@/components/ui/use-toast";
58
62
  import { useCellActions } from "@/core/cells/cells";
59
63
  import { useLastFocusedCellId } from "@/core/cells/focus";
@@ -61,6 +65,8 @@ import { disableFileDownloadsAtom } from "@/core/config/config";
61
65
  import { useRequestClient } from "@/core/network/requests";
62
66
  import { isWasm } from "@/core/wasm/utils";
63
67
  import { useAsyncData } from "@/hooks/useAsyncData";
68
+ import { useFileSearch } from "./use-file-search";
69
+ import { useHoverExpand } from "./use-hover-expand";
64
70
  import { ErrorBanner } from "@/plugins/impl/common/error-banner";
65
71
  import { cn } from "@/utils/cn";
66
72
  import { copyToClipboard } from "@/utils/copy";
@@ -71,6 +77,8 @@ import { jotaiJsonStorage } from "@/utils/storage/jotai";
71
77
  import { useTreeDndManager } from "./dnd-wrapper";
72
78
  import { downloadFile } from "./download";
73
79
  import { FileViewer } from "./file-viewer";
80
+ import { FileSearchResults } from "./file-search-results";
81
+ import { FileBrowserRow } from "./file-browser-row";
74
82
  import type { FileTreeNode, RequestingTree } from "./requesting-tree";
75
83
  import { openStateAtom, treeAtom } from "./state";
76
84
  import { PYTHON_CODE_FOR_FILE_TYPE } from "./types";
@@ -102,6 +110,10 @@ export const FileExplorer: React.FC<{
102
110
  externalDropDestinationPath?: FilePath | null;
103
111
  }> = ({ height, externalDropDestinationPath = null }) => {
104
112
  const treeRef = useRef<TreeApi<FileTreeNode>>(null);
113
+ const searchTreeRef = useRef<TreeApi<FileTreeNode>>(null);
114
+ const searchRef = useRef<HTMLInputElement>(null);
115
+ const [query, setQuery] = useState("");
116
+ const [revealId, setRevealId] = useState<string | null>(null);
105
117
  const dndManager = useTreeDndManager();
106
118
  const [tree] = useAtom(treeAtom);
107
119
  const [data, setData] = useState<FileTreeNode[]>([]);
@@ -112,6 +124,7 @@ export const FileExplorer: React.FC<{
112
124
  } | null>(null);
113
125
  const [showHiddenFiles, setShowHiddenFiles] =
114
126
  useAtom<boolean>(hiddenFilesState);
127
+ const search = useFileSearch({ query: query.trim(), tree, showHiddenFiles });
115
128
  // Keep external state to remember which folders are open when this
116
129
  // component is unmounted.
117
130
  const [openState, setOpenState] = useAtom(openStateAtom);
@@ -135,14 +148,12 @@ export const FileExplorer: React.FC<{
135
148
  openUploadPicker();
136
149
  });
137
150
 
138
- const { openPrompt } = useImperativeModal();
151
+ const { openPrompt, openConfirm } = useImperativeModal();
139
152
  const { isPending, error } = useAsyncData(() => tree.initialize(setData), []);
140
153
 
141
- const handleRefresh = useEvent(() => {
142
- // Return the promise so callers can await refresh completion
143
- return tree.refreshAll(
144
- Object.keys(openState).filter((id) => openState[id]),
145
- );
154
+ const handleRefresh = useEvent(async () => {
155
+ await tree.refreshAll(Object.keys(openState).filter((id) => openState[id]));
156
+ search.refetch();
146
157
  });
147
158
 
148
159
  const handleHiddenFilesToggle = useEvent(() => {
@@ -204,6 +215,77 @@ export const FileExplorer: React.FC<{
204
215
  [tree, handleUploadFiles, externalDropDestinationPath],
205
216
  );
206
217
 
218
+ React.useEffect(() => {
219
+ if (revealId && !openFile) {
220
+ const activeTree = query.trim() ? searchTreeRef.current : treeRef.current;
221
+ activeTree?.openParents(revealId);
222
+ activeTree?.select(revealId);
223
+ const node = activeTree?.get(revealId);
224
+ if (node?.data.isDirectory) {
225
+ node.open();
226
+ }
227
+ setRevealId(null);
228
+ }
229
+ }, [revealId, query, openFile, data]);
230
+
231
+ const handleReveal = async (node: FileTreeNode) => {
232
+ if (!node.isDirectory) {
233
+ setOpenFile(node);
234
+ return;
235
+ }
236
+ if (await tree.reveal(node)) {
237
+ setQuery("");
238
+ setRevealId(node.id);
239
+ }
240
+ };
241
+
242
+ const handleTreeKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
243
+ if ((event.ctrlKey || event.metaKey) && event.key === "f") {
244
+ event.preventDefault();
245
+ event.stopPropagation();
246
+ searchRef.current?.focus();
247
+ searchRef.current?.select();
248
+ return;
249
+ }
250
+ if (event.key === "Escape" && query) {
251
+ event.preventDefault();
252
+ event.stopPropagation();
253
+ setQuery("");
254
+ searchRef.current?.focus();
255
+ return;
256
+ }
257
+ const role =
258
+ event.target instanceof Element
259
+ ? event.target.getAttribute("role")
260
+ : null;
261
+ if (query.trim() || (role !== "treeitem" && role !== "tree")) {
262
+ return;
263
+ }
264
+ const node = treeRef.current?.focusedNode;
265
+ if (!node || treeRef.current?.isEditing) {
266
+ return;
267
+ }
268
+ if (event.key === "Enter") {
269
+ event.preventDefault();
270
+ event.stopPropagation();
271
+ if (node.data.isDirectory) {
272
+ node.toggle();
273
+ } else {
274
+ node.activate();
275
+ }
276
+ } else if (event.key === "F2" && !node.data.isRoot) {
277
+ event.preventDefault();
278
+ event.stopPropagation();
279
+ node.edit();
280
+ } else if (event.key === "Delete" || event.key === "Backspace") {
281
+ event.preventDefault();
282
+ event.stopPropagation();
283
+ if (!node.data.isRoot) {
284
+ void node.tree.delete(node.id);
285
+ }
286
+ }
287
+ };
288
+
207
289
  if (isPending) {
208
290
  return <Spinner size="medium" centered={true} />;
209
291
  }
@@ -212,128 +294,230 @@ export const FileExplorer: React.FC<{
212
294
  return <ErrorBanner error={error} />;
213
295
  }
214
296
 
215
- if (openFile) {
216
- return (
217
- <>
218
- <div className="flex items-center pl-1 pr-3 shrink-0 border-b justify-between">
219
- <Button
220
- onClick={() => setOpenFile(null)}
221
- data-testid="file-explorer-back-button"
222
- variant="text"
223
- size="xs"
224
- className="mb-0"
225
- >
226
- <ArrowLeftIcon size={16} />
227
- </Button>
228
- <span className="font-bold">{openFile.name}</span>
229
- </div>
230
- <Suspense>
231
- <FileViewer
232
- onOpenNotebook={
233
- tree.isPrimaryNode(openFile)
234
- ? (evt) => {
235
- const path = tree.getPrimaryRelativePath(
236
- openFile.path as FilePath,
237
- );
238
- if (path !== null) {
239
- openMarimoNotebook(evt, path);
240
- }
297
+ const preview = openFile ? (
298
+ <>
299
+ <div className="flex items-center pl-1 pr-3 shrink-0 border-b justify-between">
300
+ <Button
301
+ autoFocus={true}
302
+ aria-label="Back to files"
303
+ onClick={() => {
304
+ setRevealId(openFile.id);
305
+ setOpenFile(null);
306
+ }}
307
+ data-testid="file-explorer-back-button"
308
+ variant="text"
309
+ size="xs"
310
+ className="mb-0"
311
+ >
312
+ <ArrowLeftIcon size={16} />
313
+ </Button>
314
+ <span className="font-bold">{openFile.name}</span>
315
+ </div>
316
+ <Suspense>
317
+ <FileViewer
318
+ onOpenNotebook={
319
+ tree.isPrimaryNode(openFile)
320
+ ? (evt) => {
321
+ const path = tree.getPrimaryRelativePath(
322
+ openFile.path as FilePath,
323
+ );
324
+ if (path !== null) {
325
+ openMarimoNotebook(evt, path);
241
326
  }
242
- : undefined
243
- }
244
- file={openFile}
245
- />
246
- </Suspense>
247
- </>
248
- );
249
- }
327
+ }
328
+ : undefined
329
+ }
330
+ file={openFile}
331
+ />
332
+ </Suspense>
333
+ </>
334
+ ) : null;
250
335
 
251
336
  return (
252
337
  <>
253
- <input
254
- data-testid="file-explorer-upload-input"
255
- {...getUploadInputProps()}
256
- />
257
- <Toolbar
258
- onRefresh={handleRefresh}
259
- onHidden={handleHiddenFilesToggle}
260
- showHiddenFiles={showHiddenFiles}
261
- onCreateFile={handleCreateFile}
262
- onCreateNotebook={handleCreateNotebook}
263
- onCreateFolder={handleCreateFolder}
264
- onCollapseAll={handleCollapseAll}
265
- uploadDestinationPath={
266
- selectedFolder?.path ?? tree.getPrimaryRootPath()
267
- }
268
- uploadDestinationLabel={getUploadDestinationLabel(
269
- tree,
270
- selectedFolder?.path ?? tree.getPrimaryRootPath(),
271
- )}
272
- onUpload={handleUploadFiles}
273
- />
274
- <RequestingTreeContext value={contextValue}>
275
- <Tree<FileTreeNode>
276
- width="100%"
277
- ref={treeRef}
278
- height={height - 33}
279
- className="h-full"
280
- data={visibleData}
281
- initialOpenState={openState}
282
- openByDefault={false}
283
- // Use shared DnD manager to prevent "Cannot have two HTML5 backends" error
284
- dndManager={dndManager}
285
- // Hide the drop cursor
286
- renderCursor={() => null}
287
- // Disable dropping files into files
288
- disableDrag={(node) => node.isRoot}
289
- disableDrop={({ parentNode, dragNodes }) =>
290
- dragNodes.some((node) => node.data.isRoot) ||
291
- (parentNode ? !parentNode.data.isDirectory : false)
292
- }
293
- onDelete={async ({ ids }) => {
294
- for (const id of ids) {
295
- await tree.delete(id);
338
+ {preview}
339
+ <div hidden={!!openFile} onKeyDownCapture={handleTreeKeyDown}>
340
+ <input
341
+ data-testid="file-explorer-upload-input"
342
+ {...getUploadInputProps()}
343
+ />
344
+ <div>
345
+ <Toolbar
346
+ onRefresh={handleRefresh}
347
+ onHidden={handleHiddenFilesToggle}
348
+ showHiddenFiles={showHiddenFiles}
349
+ onCreateFile={handleCreateFile}
350
+ onCreateNotebook={handleCreateNotebook}
351
+ onCreateFolder={handleCreateFolder}
352
+ onCollapseAll={handleCollapseAll}
353
+ uploadDestinationPath={
354
+ selectedFolder?.path ?? tree.getPrimaryRootPath()
296
355
  }
297
- }}
298
- onRename={async ({ id, name }) => {
299
- await tree.rename(id, name);
300
- }}
301
- onMove={async ({ dragIds, parentId }) => {
302
- await tree.move(dragIds, parentId);
303
- }}
304
- onSelect={(nodes) => {
305
- const first = nodes[0];
306
- if (!first) {
307
- setSelectedFolder(null);
308
- return;
309
- }
310
- if (first.data.isDirectory) {
311
- setSelectedFolder({
312
- id: first.id,
313
- path: first.data.path as FilePath,
314
- });
315
- return;
316
- }
317
- setSelectedFolder(null);
318
- setOpenFile(first.data);
319
- }}
320
- onToggle={async (id) => {
321
- const result = await tree.expand(id);
322
- if (result) {
323
- const prevOpen = openState[id] ?? false;
324
- setOpenState({ ...openState, [id]: !prevOpen });
325
- }
326
- }}
327
- padding={15}
328
- rowHeight={30}
329
- indent={INDENT_STEP}
330
- overscanCount={1000}
331
- // Disable multi-selection
332
- disableMultiSelection={true}
333
- >
334
- {Node}
335
- </Tree>
336
- </RequestingTreeContext>
356
+ uploadDestinationLabel={getUploadDestinationLabel(
357
+ tree,
358
+ selectedFolder?.path ?? tree.getPrimaryRootPath(),
359
+ )}
360
+ onUpload={handleUploadFiles}
361
+ />
362
+ <div className="relative -mt-1">
363
+ <SearchInput
364
+ ref={searchRef}
365
+ className="rounded-none pr-6 focus-visible:outline-1 focus-visible:outline-ring/50"
366
+ clearable={false}
367
+ aria-label="Search files and folders"
368
+ placeholder="Search"
369
+ title="Search names in all configured roots, up to 20 folders deep. Generated directories such as node_modules and .git are excluded."
370
+ value={query}
371
+ onChange={(event) => setQuery(event.target.value)}
372
+ onKeyDown={(event) => {
373
+ const activeTree = query.trim()
374
+ ? searchTreeRef.current
375
+ : treeRef.current;
376
+ if (event.key === "ArrowDown") {
377
+ event.preventDefault();
378
+ activeTree?.focus(activeTree.firstNode);
379
+ } else if (event.key === "Enter" && query.trim()) {
380
+ event.preventDefault();
381
+ activeTree?.firstNode?.activate();
382
+ }
383
+ }}
384
+ />
385
+ {query && (
386
+ <Button
387
+ variant="text"
388
+ size="icon"
389
+ className="absolute right-3 top-1/2 h-5 w-5 -translate-y-1/2"
390
+ aria-label="Clear search"
391
+ onClick={() => {
392
+ setQuery("");
393
+ searchRef.current?.focus();
394
+ }}
395
+ >
396
+ <XIcon className="h-3 w-3" />
397
+ </Button>
398
+ )}
399
+ </div>
400
+ </div>
401
+ <div hidden={!query.trim()}>
402
+ <FileSearchResults
403
+ treeRef={searchTreeRef}
404
+ search={search}
405
+ height={Math.max(0, height - 63)}
406
+ onReveal={handleReveal}
407
+ />
408
+ </div>
409
+ <div hidden={!!query.trim()} className="relative">
410
+ <RequestingTreeContext value={contextValue}>
411
+ <Tree<FileTreeNode>
412
+ width="100%"
413
+ ref={treeRef}
414
+ height={Math.max(0, height - 63)}
415
+ className="h-full"
416
+ data={visibleData}
417
+ childrenAccessor={(node) =>
418
+ node.isDirectory ? node.children : null
419
+ }
420
+ selectionFollowsFocus={true}
421
+ rowClassName="outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring"
422
+ renderRow={FileBrowserRow}
423
+ renderDragPreview={(props) => (
424
+ <FileDragPreview {...props} tree={treeRef.current} />
425
+ )}
426
+ disableEdit={(node) => node.isRoot}
427
+ initialOpenState={openState}
428
+ openByDefault={false}
429
+ // Use shared DnD manager to prevent "Cannot have two HTML5 backends" error
430
+ dndManager={dndManager}
431
+ // Hide the drop cursor
432
+ renderCursor={() => null}
433
+ // Disable dropping files into files
434
+ disableDrag={(node) => node.isRoot}
435
+ disableDrop={({ parentNode, dragNodes }) =>
436
+ dragNodes.some((node) => node.data.isRoot) ||
437
+ (parentNode ? !parentNode.data.isDirectory : false)
438
+ }
439
+ onDelete={async ({ ids }) => {
440
+ openConfirm({
441
+ title: "Delete file",
442
+ description:
443
+ "Are you sure you want to delete the selected file or folder?",
444
+ confirmAction: (
445
+ <AlertDialogDestructiveAction
446
+ onClick={async () => {
447
+ for (const id of ids) {
448
+ await tree.delete(id);
449
+ }
450
+ }}
451
+ >
452
+ Delete
453
+ </AlertDialogDestructiveAction>
454
+ ),
455
+ });
456
+ }}
457
+ onRename={async ({ id, name }) => {
458
+ await tree.rename(id, name);
459
+ }}
460
+ onMove={async ({ dragIds, parentId }) => {
461
+ await tree.move(dragIds, parentId);
462
+ }}
463
+ onSelect={(nodes) => {
464
+ const first = nodes[0];
465
+ if (!first) {
466
+ setSelectedFolder(null);
467
+ return;
468
+ }
469
+ if (first.data.isDirectory) {
470
+ setSelectedFolder({
471
+ id: first.id,
472
+ path: first.data.path as FilePath,
473
+ });
474
+ return;
475
+ }
476
+ setSelectedFolder(null);
477
+ }}
478
+ onActivate={(node) => {
479
+ if (!node.data.isDirectory) {
480
+ setOpenFile(node.data);
481
+ }
482
+ }}
483
+ onToggle={async (id) => {
484
+ const isOpen =
485
+ treeRef.current?.isOpen(id) ?? !(openState[id] ?? false);
486
+ if (!isOpen) {
487
+ setOpenState((previous) => ({ ...previous, [id]: false }));
488
+ return;
489
+ }
490
+ const loaded = await tree.expand(id);
491
+ if (!loaded) {
492
+ treeRef.current?.close(id);
493
+ }
494
+ const remainsOpen =
495
+ loaded && (treeRef.current?.isOpen(id) ?? false);
496
+ setOpenState((previous) => ({
497
+ ...previous,
498
+ [id]: remainsOpen,
499
+ }));
500
+ }}
501
+ padding={15}
502
+ rowHeight={30}
503
+ indent={INDENT_STEP}
504
+ overscanCount={8}
505
+ // Disable multi-selection
506
+ disableMultiSelection={true}
507
+ >
508
+ {Node}
509
+ </Tree>
510
+ </RequestingTreeContext>
511
+ {!visibleData.length && (
512
+ <p
513
+ role="status"
514
+ className="absolute top-2 left-3 text-sm text-muted-foreground"
515
+ >
516
+ No visible files.
517
+ </p>
518
+ )}
519
+ </div>
520
+ </div>
337
521
  </>
338
522
  );
339
523
  };
@@ -368,7 +552,7 @@ const Toolbar = ({
368
552
  const uploadLabel = `Upload files to ${uploadDestinationLabel}`;
369
553
 
370
554
  return (
371
- <div className="flex items-center justify-end px-2 shrink-0 border-b">
555
+ <div className="flex items-center justify-end px-2 shrink-0">
372
556
  <Tooltip content="Add notebook">
373
557
  <Button
374
558
  data-testid="file-explorer-add-notebook-button"
@@ -445,16 +629,7 @@ const Show = ({
445
629
  ) => void;
446
630
  }) => {
447
631
  const label = (
448
- <span
449
- className="flex-1 overflow-hidden text-ellipsis"
450
- onClick={(e) => {
451
- if (node.data.isDirectory) {
452
- return;
453
- }
454
- e.stopPropagation();
455
- node.select();
456
- }}
457
- >
632
+ <span className="flex-1 overflow-hidden text-ellipsis">
458
633
  {node.data.name}
459
634
  {node.data.isMarimoFile && node.data.isPrimaryRoot && !isWasm() && (
460
635
  <span
@@ -484,7 +659,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileTreeNode>) => {
484
659
  : guessFileIconType(node.data.name);
485
660
 
486
661
  const Icon = FILE_ICON[fileType];
487
- const { openConfirm, openPrompt } = useImperativeModal();
662
+ const { openPrompt } = useImperativeModal();
488
663
  const { createNewCell } = useCellActions();
489
664
  const lastFocusedCellId = useLastFocusedCellId();
490
665
 
@@ -501,6 +676,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileTreeNode>) => {
501
676
  const isExternalDropTarget =
502
677
  node.data.isDirectory &&
503
678
  fileExplorer?.externalDropDestinationPath === node.data.path;
679
+ useHoverExpand(node, isExternalDropTarget);
504
680
 
505
681
  const handleOpenMarimoFile = async (
506
682
  evt: Pick<Event, "stopPropagation" | "preventDefault">,
@@ -514,20 +690,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileTreeNode>) => {
514
690
  const handleDeleteFile = async (evt: Event) => {
515
691
  evt.stopPropagation();
516
692
  evt.preventDefault();
517
- openConfirm({
518
- title: "Delete file",
519
- description: `Are you sure you want to delete ${node.data.name}?`,
520
- confirmAction: (
521
- <AlertDialogDestructiveAction
522
- onClick={async () => {
523
- await node.tree.delete(node.id);
524
- }}
525
- aria-label="Confirm"
526
- >
527
- Delete
528
- </AlertDialogDestructiveAction>
529
- ),
530
- });
693
+ await node.tree.delete(node.id);
531
694
  };
532
695
 
533
696
  const handleCreateFolder = useEvent(async () => {
@@ -576,40 +739,53 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileTreeNode>) => {
576
739
  ? { [FILE_EXPLORER_DIRECTORY_PATH_ATTRIBUTE]: node.data.path }
577
740
  : {})}
578
741
  className={cn(
579
- "flex items-center cursor-pointer ml-1 text-muted-foreground whitespace-nowrap group",
742
+ "relative flex h-full items-center cursor-pointer ml-1 text-muted-foreground whitespace-nowrap group",
580
743
  )}
581
744
  draggable={!node.data.isRoot}
582
- onClick={(evt) => {
583
- evt.stopPropagation();
745
+ onClick={() => {
584
746
  if (node.data.isDirectory) {
585
- node.select();
586
747
  node.toggle();
587
748
  }
588
749
  }}
750
+ onDoubleClick={(event) => {
751
+ event.stopPropagation();
752
+ if (!node.data.isDirectory) {
753
+ node.activate();
754
+ }
755
+ }}
589
756
  >
757
+ {Array.from({ length: node.level }, (_, level) => (
758
+ <span
759
+ key={level}
760
+ aria-hidden="true"
761
+ className="pointer-events-none absolute inset-y-0 border-l border-border"
762
+ style={{ left: level * INDENT_STEP + 8 }}
763
+ />
764
+ ))}
590
765
  <FolderArrow node={node} />
591
766
  <span
592
767
  className={cn(
593
768
  "flex items-center pl-1 py-1 cursor-pointer hover:bg-accent/50 hover:text-accent-foreground rounded-l flex-1 overflow-hidden group",
594
769
  node.willReceiveDrop &&
770
+ node.tree.canDrop() &&
595
771
  node.data.isDirectory &&
596
- "bg-accent/80 hover:bg-accent/80 text-accent-foreground",
772
+ "bg-primary/15 hover:bg-primary/15 text-accent-foreground",
597
773
  node.isSelected &&
598
774
  "bg-accent/60 hover:bg-accent/60 text-accent-foreground",
599
775
  isExternalDropTarget &&
600
- "bg-primary/15 hover:bg-primary/15 text-accent-foreground ring-1 ring-inset ring-primary",
776
+ "bg-primary/15 hover:bg-primary/15 text-accent-foreground",
601
777
  )}
602
778
  >
603
779
  {node.data.isRoot ? (
604
780
  <FolderRootIcon
605
- className="w-5 h-5 shrink-0 mr-2 text-primary"
781
+ className="w-4 h-4 shrink-0 mr-2 text-primary"
606
782
  strokeWidth={1.5}
607
783
  />
608
784
  ) : node.data.isMarimoFile ? (
609
- <MarimoIcon className="w-5 h-5 shrink-0 mr-2" strokeWidth={1.5} />
785
+ <MarimoIcon className="w-4 h-4 shrink-0 mr-2" strokeWidth={1.5} />
610
786
  ) : (
611
787
  <Icon
612
- className={cn("w-5 h-5 shrink-0 mr-2", FILE_ICON_COLOR[fileType])}
788
+ className={cn("w-4 h-4 shrink-0 mr-2", FILE_ICON_COLOR[fileType])}
613
789
  strokeWidth={1.5}
614
790
  />
615
791
  )}
@@ -624,7 +800,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileTreeNode>) => {
624
800
  >
625
801
  {!node.data.isDirectory && (
626
802
  <DropdownMenuItem
627
- onSelect={() => node.select()}
803
+ onSelect={() => node.activate()}
628
804
  data-testid="file-explorer-open-file-menu-item"
629
805
  >
630
806
  <ViewIcon className={MENU_ITEM_ICON_CLASS} />
@@ -794,6 +970,18 @@ const FolderArrow = ({ node }: { node: NodeApi<FileTreeNode> }) => {
794
970
  return <span className="w-4 h-4 shrink-0" />;
795
971
  }
796
972
 
973
+ if (node.data.loadState === "loading") {
974
+ return (
975
+ <span
976
+ role="status"
977
+ aria-label={`Loading ${node.data.name}`}
978
+ className="w-4 h-4 shrink-0"
979
+ >
980
+ <Spinner size="small" />
981
+ </span>
982
+ );
983
+ }
984
+
797
985
  return <TreeChevron isExpanded={node.isOpen} className="w-4 h-4" />;
798
986
  };
799
987
 
@@ -852,3 +1040,33 @@ function treeContainsId(list: FileTreeNode[], id: string): boolean {
852
1040
  (item.children ? treeContainsId(item.children, id) : false),
853
1041
  );
854
1042
  }
1043
+
1044
+ function FileDragPreview({
1045
+ mouse,
1046
+ id,
1047
+ isDragging,
1048
+ tree,
1049
+ }: DragPreviewProps & { tree: TreeApi<FileTreeNode> | null }) {
1050
+ const node = tree?.get(id);
1051
+ if (!isDragging || !mouse || !node) {
1052
+ return null;
1053
+ }
1054
+ const fileType = node.data.isDirectory
1055
+ ? "directory"
1056
+ : guessFileIconType(node.data.name);
1057
+ const Icon = FILE_ICON[fileType];
1058
+ return createPortal(
1059
+ <div
1060
+ className="pointer-events-none fixed left-0 top-0 z-[1000] opacity-[0.85]"
1061
+ style={{
1062
+ transform: `translate3d(${mouse.x + 12}px, ${mouse.y + 12}px, 0)`,
1063
+ }}
1064
+ >
1065
+ <div className="flex max-w-72 items-center gap-2 rounded-md border border-border bg-popover px-3 py-2 text-sm text-popover-foreground shadow-md">
1066
+ <Icon className={cn("h-4 w-4 shrink-0", FILE_ICON_COLOR[fileType])} />
1067
+ <span className="truncate">{node.data.name}</span>
1068
+ </div>
1069
+ </div>,
1070
+ document.body,
1071
+ );
1072
+ }