@marimo-team/frontend 0.23.16-dev3 → 0.23.16-dev34

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 (230) hide show
  1. package/dist/assets/{CellStatus-BalYv0pK.js → CellStatus-CmojVYzm.js} +1 -1
  2. package/dist/assets/{ConnectedDataExplorerComponent-DI7mV-Gw.js → ConnectedDataExplorerComponent-CMaZapRd.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-C7_5jjWi.js → ErrorBoundary-DzTD8LVq.js} +1 -1
  4. package/dist/assets/{ImperativeModal-BIavOiUv.js → ImperativeModal-fY-ZfT0b.js} +1 -1
  5. package/dist/assets/{JsonOutput-CkCD-FgM.js → JsonOutput-BwwRpByA.js} +12 -12
  6. package/dist/assets/{LazyAnyLanguageCodeMirror-sVm_hz8A.js → LazyAnyLanguageCodeMirror-B2JvSXP-.js} +2 -2
  7. package/dist/assets/{MarimoErrorOutput-DO2k2fA5.js → MarimoErrorOutput-BWte8vjk.js} +4 -4
  8. package/dist/assets/{RSPContexts-CgC81f0I.js → RSPContexts-WiCgGm__.js} +1 -1
  9. package/dist/assets/RenderHTML-DRltV71H.js +1 -0
  10. package/dist/assets/{RunButton-BKRupqr7.js → RunButton-DIegd2mN.js} +1 -1
  11. package/dist/assets/{add-cell-with-ai-CEZtaZCK.js → add-cell-with-ai-BBFD-M9V.js} +17 -17
  12. package/dist/assets/add-connection-dialog-CECJ8507.js +170 -0
  13. package/dist/assets/{agent-panel-CHB6t7Zy.js → agent-panel-YaS0WXP1.js} +5 -5
  14. package/dist/assets/ai-model-dropdown-BpVBUZnz.js +5 -0
  15. package/dist/assets/{alert-dialog-BXegNC0I.js → alert-dialog-Bb8Lz8ko.js} +1 -1
  16. package/dist/assets/{any-language-editor-DCpZRrpx.js → any-language-editor--tRHs8C_.js} +1 -1
  17. package/dist/assets/{app-config-button-NrM9ywXA.js → app-config-button-BW-w01aF.js} +1 -1
  18. package/dist/assets/{button-eIihcbUw.js → button-DDGiD1NW.js} +1 -1
  19. package/dist/assets/{cache-panel-wllSTlbM.js → cache-panel-DcQUeUpg.js} +1 -1
  20. package/dist/assets/{capabilities-B09j1Eor.js → capabilities-DoEg0XwL.js} +1 -1
  21. package/dist/assets/{cell-editor-B5JVq7_q.js → cell-editor-EV-1IccQ.js} +10 -10
  22. package/dist/assets/cell-link-C5gpAn3R.js +1 -0
  23. package/dist/assets/cells-6yd8nOAu.js +231 -0
  24. package/dist/assets/{chat-display-BG08ltQ3.js → chat-display-8BbC5B-D.js} +1 -1
  25. package/dist/assets/{chat-panel-P2CzCtXJ.js → chat-panel-CMOZp5xW.js} +3 -3
  26. package/dist/assets/{chat-ui-fAJZAoTD.js → chat-ui-DWhJ2Ozy.js} +1 -1
  27. package/dist/assets/{column-preview-Dne1dIt0.js → column-preview-DeXESYpM.js} +1 -1
  28. package/dist/assets/{command-CAJLPEBq.js → command-Dqxie4gq.js} +1 -1
  29. package/dist/assets/{command-palette-iOCqUIs1.js → command-palette-Dx6AkiPd.js} +1 -1
  30. package/dist/assets/{common-CD6Mj8n6.js → common-XVELED_O.js} +1 -1
  31. package/dist/assets/components-BLHpmhXx.js +1 -0
  32. package/dist/assets/config-B-Q7J_6d.js +1 -0
  33. package/dist/assets/{config-COUOIPEq.js → config-BJVx3eBW.js} +1 -1
  34. package/dist/assets/{copy-BtTOqPTS.js → copy-Bxb1_Gtw.js} +1 -1
  35. package/dist/assets/{copy-icon-C2pG0ZNK.js → copy-icon-neDQUo4Q.js} +1 -1
  36. package/dist/assets/{createReducer-C7kZ8zEe.js → createReducer-DGx5Af-C.js} +1 -1
  37. package/dist/assets/{datasource-ySAiC9gr.js → datasource-CTqa8bf-.js} +2 -2
  38. package/dist/assets/{dates-D-kIKDYA.js → dates-C2LWzsCZ.js} +1 -1
  39. package/dist/assets/{dependency-graph-panel-BF23EwcD.js → dependency-graph-panel-C4Nzcddp.js} +4 -4
  40. package/dist/assets/{dialog-D1DEsRZ2.js → dialog-BCVepWLJ.js} +1 -1
  41. package/dist/assets/{dist-ByVeMWkr.js → dist-BLN8PNJV.js} +1 -1
  42. package/dist/assets/dist-CiVShZKM.js +2 -0
  43. package/dist/assets/{documentation-panel-e9Fi9R7e.js → documentation-panel-Bym4G7IS.js} +1 -1
  44. package/dist/assets/download-HKgqJX4l.js +9 -0
  45. package/dist/assets/{dropdown-menu-BFZLnWeO.js → dropdown-menu-dHmZR8bI.js} +1 -1
  46. package/dist/assets/{edit-page-DSEkwJ4O.js → edit-page-Jtp6Qqjl.js} +6 -6
  47. package/dist/assets/{error-banner-B6eIlxzg.js → error-banner-BSxEOIn2.js} +1 -1
  48. package/dist/assets/{error-entries-Birw-8VX.js → error-entries-CxyvaxEM.js} +2 -2
  49. package/dist/assets/{error-panel-DdsUL6b7.js → error-panel-CfSy7uUr.js} +1 -1
  50. package/dist/assets/{es-1D83cgiu.js → es-gYgfF8y5.js} +1 -1
  51. package/dist/assets/{esm-BL7jQmzn.js → esm-B30N1GS0.js} +1 -1
  52. package/dist/assets/{field-BeZbVzXA.js → field-BsxD35VE.js} +1 -1
  53. package/dist/assets/file-explorer-panel-Dd5Xn2TK.js +26 -0
  54. package/dist/assets/{file-icons-Cb54mIL8.js → file-icons-CSjJ3JC6.js} +1 -1
  55. package/dist/assets/{file-name-input-BWFcK1ey.js → file-name-input-CrKtrXMl.js} +1 -1
  56. package/dist/assets/floating-outline-ByQ8vvif.js +1 -0
  57. package/dist/assets/focus-CVONl-Dy.js +1 -0
  58. package/dist/assets/{form-B6BkSCJb.js → form-Ca3yd_Od.js} +1 -1
  59. package/dist/assets/{formats-ClEFGPy-.js → formats-DzmLpc9A.js} +1 -1
  60. package/dist/assets/{formatting-B_41gSF5.js → formatting-CDf6w1Kt.js} +1 -1
  61. package/dist/assets/{fullscreen-dUjPYOmm.js → fullscreen-CTDqNQr6.js} +1 -1
  62. package/dist/assets/{gallery-page-CWQQAYpa.js → gallery-page-CJ3ms1Hn.js} +1 -1
  63. package/dist/assets/{glide-data-editor-lMARFWHS.js → glide-data-editor-j2igs9Y_.js} +1 -1
  64. package/dist/assets/home-page-FpAawj9s.js +4 -0
  65. package/dist/assets/{html-to-image-CwmX28T9.js → html-to-image-C-4ysbGe.js} +2 -2
  66. package/dist/assets/index-CiJmYQBT.js +38 -0
  67. package/dist/assets/index-xs-zSSrX.css +2 -0
  68. package/dist/assets/{input-CqrUjynb.js → input-JxFwu-3G.js} +1 -1
  69. package/dist/assets/kiosk-mode-BumOFEtY.js +1 -0
  70. package/dist/assets/{label-BElwWzf0.js → label-CqjrpwRC.js} +1 -1
  71. package/dist/assets/{layout-CUlZijwG.js → layout-Dtql_o_T.js} +5 -5
  72. package/dist/assets/{links-CToBjIIj.js → links-CZr5bjvo.js} +1 -1
  73. package/dist/assets/{logs-panel-iFV4ZJ9b.js → logs-panel-5u1wUBn1.js} +1 -1
  74. package/dist/assets/{markdown-renderer-QvXG0CRk.js → markdown-renderer-dABDqZDw.js} +2 -2
  75. package/dist/assets/{menu-items-DbSLBfXR.js → menu-items-CCi6aOPm.js} +1 -1
  76. package/dist/assets/{mermaid-Bh0flEIb.js → mermaid-CYN0wOyG.js} +1 -1
  77. package/dist/assets/name-cell-input-Y4knmEIU.js +1 -0
  78. package/dist/assets/node-sql-parser-Si4oZ2P-.js +68 -0
  79. package/dist/assets/{numbers-CeeaFKqu.js → numbers-rmawRqQP.js} +1 -1
  80. package/dist/assets/outline-panel-CQ_KWLip.js +1 -0
  81. package/dist/assets/{packages-panel-BzQF60rG.js → packages-panel-Ijt7d51P.js} +1 -1
  82. package/dist/assets/pair-with-agent-modal-BCRSACei.js +4 -0
  83. package/dist/assets/panels-DfU1Oikj.js +4 -0
  84. package/dist/assets/{popover-DrHQ3KEt.js → popover-Bcz1Zikb.js} +1 -1
  85. package/dist/assets/process-output-Dd11YI04.js +1 -0
  86. package/dist/assets/{radio-group-CLBjGkt5.js → radio-group-22eWJ9fv.js} +1 -1
  87. package/dist/assets/readonly-code-display-BjK-TEri.js +1 -0
  88. package/dist/assets/{readonly-python-code-CW_G5e4E.js → readonly-python-code-n_TrLmsR.js} +1 -1
  89. package/dist/assets/{renderShortcut-BZLr8D7H.js → renderShortcut-Bl8_FVn8.js} +1 -1
  90. package/dist/assets/{reveal-component-BLMTmd8T.js → reveal-component-CCLTV1q6.js} +60 -60
  91. package/dist/assets/{run-page-B6pgDYEJ.js → run-page-DbezK7Qs.js} +1 -1
  92. package/dist/assets/{runs-D51Xwrsk.js → runs-DBTXqJHS.js} +1 -1
  93. package/dist/assets/scratchpad-panel-C2wAEv5n.js +1 -0
  94. package/dist/assets/{scroll-to-bottom-button-Cgr10kaJ.js → scroll-to-bottom-button-C8yfT4wq.js} +1 -1
  95. package/dist/assets/{secrets-panel-Cc5KDBk6.js → secrets-panel-6J6wcuVR.js} +1 -1
  96. package/dist/assets/{select-B5TFNjmU.js → select-BP75_5jJ.js} +1 -1
  97. package/dist/assets/{session-CEFnN9Cq.js → session-BmukRjSi.js} +1 -1
  98. package/dist/assets/{session-panel-Y0Oon0sD.js → session-panel-BZGNcZmp.js} +1 -1
  99. package/dist/assets/{share-CYpcX4N4.js → share-852X_cqy.js} +1 -1
  100. package/dist/assets/{skeleton-D3QPxqWC.js → skeleton-BzlKcq21.js} +1 -1
  101. package/dist/assets/slides-DkEgzTJB.css +1 -0
  102. package/dist/assets/{snippets-panel-CXpGflMO.js → snippets-panel-DNaNtkR0.js} +1 -1
  103. package/dist/assets/{spec-CzqcmYQk.js → spec-C7opZZRp.js} +1 -1
  104. package/dist/assets/{state-BvdK8eiw.js → state-D10U2MwA.js} +1 -1
  105. package/dist/assets/{state-WBgc7MiA.js → state-DDag0B_B.js} +1 -1
  106. package/dist/assets/state-DFPAfLw7.js +3 -0
  107. package/dist/assets/{state-DBzEQtWz.js → state-jCdh8Dcx.js} +1 -1
  108. package/dist/assets/{strings-Djnbwmfl.js → strings--BknHX9C.js} +1 -1
  109. package/dist/assets/{swiper-component-DInvCxhI.js → swiper-component-g8EXE911.js} +1 -1
  110. package/dist/assets/switch-OVajJBPR.js +1 -0
  111. package/dist/assets/{terminal-D3IQBBYo.js → terminal-217O8hlZ.js} +1 -1
  112. package/dist/assets/{textarea-CpFfEUet.js → textarea-DuEtSOCl.js} +1 -1
  113. package/dist/assets/{tooltip-CFX4fYHU.js → tooltip-4bZlU7jl.js} +1 -1
  114. package/dist/assets/{tooltip-HLb6RxVp.js → tooltip-B7U4xkIN.js} +1 -1
  115. package/dist/assets/{tracing-panel-BU-o_LCd.js → tracing-panel-rVAa3En0.js} +2 -2
  116. package/dist/assets/{tracing-CKn3gWHw.js → tracing-ttUd3J7U.js} +1 -1
  117. package/dist/assets/{tree-actions-CsC2XmGo.js → tree-actions-UCr1tMHw.js} +1 -1
  118. package/dist/assets/{useBoolean-D89u30n-.js → useBoolean-CFj86iac.js} +1 -1
  119. package/dist/assets/useCellActionButton-CJknR0XY.js +1 -0
  120. package/dist/assets/{useDeleteCell-EPH4kcG7.js → useDeleteCell-PrgejW8A.js} +1 -1
  121. package/dist/assets/{useDependencyPanelTab-CZnK7N2L.js → useDependencyPanelTab-5jc-_-hy.js} +1 -1
  122. package/dist/assets/useEventListener-C0195JsF.js +1 -0
  123. package/dist/assets/{useHotkey-DIuRH8Wu.js → useHotkey-DLZvrRZT.js} +1 -1
  124. package/dist/assets/{useIframeCapabilities-Ccb4fvKc.js → useIframeCapabilities-j6fR_dBA.js} +1 -1
  125. package/dist/assets/{useInstallPackage-BRbUAUFn.js → useInstallPackage-WiYBwhUO.js} +1 -1
  126. package/dist/assets/{useInterval-CC0vKpWm.js → useInterval-CWBTUy5W.js} +1 -1
  127. package/dist/assets/useNotebookActions-4vw_NiRs.js +12 -0
  128. package/dist/assets/useRunCells-DHsd7HGL.js +1 -0
  129. package/dist/assets/{useSplitCell-9Qz93yPZ.js → useSplitCell-MZpER0Bh.js} +1 -1
  130. package/dist/assets/{useTheme-KXqAyDPC.js → useTheme-Bxv9B31g.js} +1 -1
  131. package/dist/assets/{utils-Z80SV4_d.js → utils-BWBBpCkT.js} +1 -1
  132. package/dist/assets/{vega-component-Cie3ag_T.js → vega-component-Db8KjZJG.js} +1 -1
  133. package/dist/assets/{write-secret-modal-DEEn7sZV.js → write-secret-modal-3-v-dHVj.js} +1 -1
  134. package/dist/index.html +68 -68
  135. package/package.json +2 -2
  136. package/src/__mocks__/requests.ts +45 -4
  137. package/src/components/data-table/__tests__/__snapshots__/chart-spec-model.test.ts.snap +174 -21
  138. package/src/components/data-table/__tests__/chart-spec-model.test.ts +102 -1
  139. package/src/components/data-table/column-summary/chart-spec-model.tsx +163 -103
  140. package/src/components/data-table/column-summary/legacy-chart-spec.ts +15 -7
  141. package/src/components/data-table/column-summary/utils.ts +26 -7
  142. package/src/components/data-table/types.ts +1 -1
  143. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +99 -18
  144. package/src/components/editor/actions/__tests__/pdf-export.test.ts +0 -4
  145. package/src/components/editor/actions/pair-with-agent-commands.ts +21 -10
  146. package/src/components/editor/actions/pair-with-agent-modal.tsx +2 -2
  147. package/src/components/editor/actions/pdf-export.ts +1 -4
  148. package/src/components/editor/actions/useNotebookActions.tsx +7 -13
  149. package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +50 -0
  150. package/src/components/editor/connections/storage/__tests__/as-code.test.ts +87 -8
  151. package/src/components/editor/connections/storage/as-code.ts +40 -21
  152. package/src/components/editor/connections/storage/schemas.ts +83 -42
  153. package/src/components/editor/file-tree/__tests__/file-render-mode.test.ts +27 -0
  154. package/src/components/editor/file-tree/__tests__/file-viewer.test.tsx +150 -0
  155. package/src/components/editor/file-tree/file-preview-metadata.tsx +51 -0
  156. package/src/components/editor/file-tree/file-render-mode.ts +32 -0
  157. package/src/components/editor/file-tree/file-viewer.tsx +76 -50
  158. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +1 -1
  159. package/src/components/slides/__tests__/cell-output-block.test.tsx +40 -0
  160. package/src/components/slides/__tests__/slide-cell-view.test.tsx +33 -0
  161. package/src/components/slides/__tests__/slide-scroll-hint.test.tsx +198 -0
  162. package/src/components/slides/reveal-component.tsx +25 -19
  163. package/src/components/slides/slide-cell-view.tsx +1 -1
  164. package/src/components/slides/slide-scroll-hint.tsx +161 -0
  165. package/src/components/slides/slides.css +4 -2
  166. package/src/components/static-html/share-modal.tsx +1 -1
  167. package/src/components/ui/input.tsx +1 -1
  168. package/src/core/codemirror/find-replace/__tests__/navigate.test.ts +42 -30
  169. package/src/core/codemirror/find-replace/navigate.ts +2 -0
  170. package/src/core/codemirror/go-to-definition/__tests__/commands.test.ts +24 -3
  171. package/src/core/codemirror/go-to-definition/commands.ts +3 -0
  172. package/src/core/codemirror/keymaps/__tests__/keymaps.test.ts +31 -2
  173. package/src/core/codemirror/keymaps/keymaps.ts +12 -0
  174. package/src/core/codemirror/language/__tests__/sql.test.ts +443 -61
  175. package/src/core/codemirror/language/languages/sql/completion-store.ts +5 -1
  176. package/src/core/codemirror/language/languages/sql/sql.ts +210 -3
  177. package/src/core/codemirror/utils.ts +28 -0
  178. package/src/core/hotkeys/hotkeys.ts +0 -2
  179. package/src/core/islands/__tests__/bridge.test.ts +107 -0
  180. package/src/core/islands/bridge.ts +16 -0
  181. package/src/core/mode.ts +10 -0
  182. package/src/core/network/__tests__/api.test.ts +42 -0
  183. package/src/core/network/__tests__/connection.test.ts +45 -0
  184. package/src/core/network/__tests__/requests-lazy.test.ts +32 -1
  185. package/src/core/network/__tests__/requests-network.test.ts +21 -0
  186. package/src/core/network/api.ts +27 -0
  187. package/src/core/network/connection.ts +15 -0
  188. package/src/core/network/requests-lazy.ts +1 -0
  189. package/src/core/network/requests-network.ts +14 -8
  190. package/src/core/network/requests-static.ts +1 -0
  191. package/src/core/network/requests-toasting.tsx +1 -0
  192. package/src/core/network/types.ts +25 -7
  193. package/src/core/static/download-html.ts +5 -14
  194. package/src/core/wasm/bridge.ts +4 -2
  195. package/src/core/wasm/worker/types.ts +5 -2
  196. package/src/plugins/impl/FormPlugin.tsx +1 -1
  197. package/src/plugins/impl/__tests__/FormPlugin.test.tsx +162 -0
  198. package/src/utils/__tests__/download.test.tsx +24 -1
  199. package/src/utils/download.ts +11 -6
  200. package/dist/assets/RenderHTML-BCvU9dYz.js +0 -1
  201. package/dist/assets/add-connection-dialog-B-jCRNTN.js +0 -170
  202. package/dist/assets/ai-model-dropdown-DXgLphFa.js +0 -5
  203. package/dist/assets/cell-link-DqBkyAFe.js +0 -1
  204. package/dist/assets/cells-Cf7bHHq3.js +0 -229
  205. package/dist/assets/components-BH6gDVv6.js +0 -1
  206. package/dist/assets/config-BbOTTCYY.js +0 -1
  207. package/dist/assets/dist-BCMBu0rO.js +0 -2
  208. package/dist/assets/download-BomYt3nl.js +0 -9
  209. package/dist/assets/file-explorer-panel-BfXM2BOd.js +0 -26
  210. package/dist/assets/floating-outline-wz-WSRgP.js +0 -1
  211. package/dist/assets/focus-DiEyfiAG.js +0 -1
  212. package/dist/assets/home-page-DJ4jJsZj.js +0 -4
  213. package/dist/assets/index-A71LAt6h.css +0 -2
  214. package/dist/assets/index-Dbc5mg7a.js +0 -38
  215. package/dist/assets/kiosk-mode-ABm3oYgq.js +0 -1
  216. package/dist/assets/name-cell-input-C6cukQ9k.js +0 -1
  217. package/dist/assets/node-sql-parser-BWgUjcfV.js +0 -68
  218. package/dist/assets/outline-panel-BPlg9_CE.js +0 -1
  219. package/dist/assets/pair-with-agent-modal-CKOYwOYw.js +0 -4
  220. package/dist/assets/panels-Dj45fW-v.js +0 -4
  221. package/dist/assets/process-output-BEhIGfkO.js +0 -1
  222. package/dist/assets/readonly-code-display-D63oRtBP.js +0 -1
  223. package/dist/assets/scratchpad-panel-B0mEqBNk.js +0 -1
  224. package/dist/assets/slides-MCbMDtlY.css +0 -1
  225. package/dist/assets/state-BZQc7xxL.js +0 -3
  226. package/dist/assets/switch-CvweaCXq.js +0 -1
  227. package/dist/assets/useCellActionButton-CRfIEVB5.js +0 -1
  228. package/dist/assets/useEventListener-B_w3A283.js +0 -1
  229. package/dist/assets/useNotebookActions-DTXAoukG.js +0 -12
  230. package/dist/assets/useRunCells-CSSF_mJ0.js +0 -1
@@ -22,6 +22,7 @@ vi.mock("../api", async () => {
22
22
  return {
23
23
  ...actual,
24
24
  API: {
25
+ handleExportResponse: vi.fn((response) => response.data),
25
26
  handleResponse: vi.fn((response) => response.data),
26
27
  handleResponseReturnNull: vi.fn(() => null),
27
28
  },
@@ -32,6 +33,7 @@ vi.mock("../api", async () => {
32
33
  vi.mock("../connection", () => ({
33
34
  isConnectedAtom: { read: vi.fn(() => true) },
34
35
  waitForConnectionOpen: vi.fn().mockResolvedValue(undefined),
36
+ waitForConnectionOpenIfNotebook: vi.fn().mockResolvedValue(undefined),
35
37
  }));
36
38
 
37
39
  describe("createNetworkRequests", () => {
@@ -121,6 +123,25 @@ describe("createNetworkRequests", () => {
121
123
  expect(mockClient.GET).toHaveBeenCalledWith("/api/environment");
122
124
  });
123
125
 
126
+ it("getExportAvailability should GET /api/export/availability", async () => {
127
+ const requests = createNetworkRequests();
128
+ await requests.getExportAvailability();
129
+
130
+ expect(mockClient.GET).toHaveBeenCalledWith("/api/export/availability");
131
+ });
132
+
133
+ it("getPackageList should not require a kernel connection", async () => {
134
+ const { waitForConnectionOpen, waitForConnectionOpenIfNotebook } =
135
+ await import("../connection");
136
+
137
+ const requests = createNetworkRequests();
138
+ await requests.getPackageList();
139
+
140
+ expect(mockClient.GET).toHaveBeenCalledWith("/api/packages/list");
141
+ expect(waitForConnectionOpenIfNotebook).toHaveBeenCalled();
142
+ expect(waitForConnectionOpen).not.toHaveBeenCalled();
143
+ });
144
+
124
145
  it("exportAsIPYNB should call the new endpoint as text", async () => {
125
146
  const requests = createNetworkRequests();
126
147
  await requests.exportAsIPYNB({
@@ -6,6 +6,7 @@ import { Logger } from "../../utils/Logger";
6
6
  import { Strings } from "../../utils/strings";
7
7
  import { getRuntimeManager } from "../runtime/config";
8
8
  import type { RuntimeManager } from "../runtime/runtime";
9
+ import type { ExportedFile } from "./types";
9
10
 
10
11
  function getBaseUriWithoutQueryParams(): string {
11
12
  // Remove query params and hash
@@ -15,6 +16,15 @@ function getBaseUriWithoutQueryParams(): string {
15
16
  return url.toString();
16
17
  }
17
18
 
19
+ function getExportFilename(response: Response): string {
20
+ const contentDisposition = response.headers.get("Content-Disposition");
21
+ const match = contentDisposition?.match(/filename\*=UTF-8''([^;]+)/i);
22
+ if (!match) {
23
+ throw new Error("Export response is missing a filename");
24
+ }
25
+ return decodeURIComponent(match[1]);
26
+ }
27
+
18
28
  /**
19
29
  * Wrapper around fetch that adds XSRF token and session ID to the request and
20
30
  * strong types.
@@ -119,6 +129,23 @@ export const API = {
119
129
  }
120
130
  return Promise.resolve(response.data as T);
121
131
  },
132
+ handleExportResponse: async <T extends BlobPart>(response: {
133
+ data?: T | undefined;
134
+ error?: Record<string, unknown>;
135
+ response: Response;
136
+ }): Promise<ExportedFile<T>> => {
137
+ const contents = await API.handleResponse<T>(response);
138
+ const mediaType = response.response.headers.get("Content-Type");
139
+ if (!mediaType) {
140
+ throw new Error("Export response is missing a media type");
141
+ }
142
+
143
+ return {
144
+ contents,
145
+ filename: getExportFilename(response.response),
146
+ mediaType,
147
+ };
148
+ },
122
149
  handleResponseReturnNull: (response: {
123
150
  error?: Record<string, unknown>;
124
151
  response: Response;
@@ -1,5 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import { atom } from "jotai";
3
+ import { isNotebookPage } from "../mode";
3
4
  import { waitFor } from "../state/jotai";
4
5
  import { type ConnectionStatus, WebSocketState } from "../websocket/types";
5
6
 
@@ -17,6 +18,20 @@ export function waitForConnectionOpen() {
17
18
  });
18
19
  }
19
20
 
21
+ /**
22
+ * Waits for the kernel connection, but only on pages that open one. Non-notebook
23
+ * pages (home, gallery) never connect, so waiting there would hang forever.
24
+ *
25
+ * Use for requests that the marimo server can serve without a session, but that
26
+ * should still wait for the kernel when a notebook is open.
27
+ */
28
+ export function waitForConnectionOpenIfNotebook() {
29
+ if (!isNotebookPage()) {
30
+ return Promise.resolve();
31
+ }
32
+ return waitForConnectionOpen();
33
+ }
34
+
20
35
  export const isConnectingAtom = atom((get) => {
21
36
  const connection = get(connectionAtom);
22
37
  return connection.state === WebSocketState.CONNECTING;
@@ -103,6 +103,7 @@ const ACTIONS: Record<keyof AllRequests, Action> = {
103
103
 
104
104
  // Served by the marimo server without a kernel session
105
105
  getEnvironmentInfo: "serverOnly",
106
+ getExportAvailability: "serverOnly",
106
107
 
107
108
  // Home operations throw errors
108
109
  getRecentFiles: "startConnection",
@@ -3,7 +3,10 @@
3
3
  import { once } from "@/utils/once";
4
4
  import { getRuntimeManager } from "../runtime/config";
5
5
  import { API, createClientWithRuntimeManager } from "./api";
6
- import { waitForConnectionOpen } from "./connection";
6
+ import {
7
+ waitForConnectionOpen,
8
+ waitForConnectionOpenIfNotebook,
9
+ } from "./connection";
7
10
  import type { EditRequests, RunRequests } from "./types";
8
11
 
9
12
  /**
@@ -19,7 +22,7 @@ function multipartInit(formData: FormData) {
19
22
  };
20
23
  }
21
24
 
22
- const { handleResponse, handleResponseReturnNull } = API;
25
+ const { handleExportResponse, handleResponse, handleResponseReturnNull } = API;
23
26
 
24
27
  export function createNetworkRequests(): EditRequests & RunRequests {
25
28
  const getClient = once(() => {
@@ -399,6 +402,9 @@ export function createNetworkRequests(): EditRequests & RunRequests {
399
402
  })
400
403
  .then(handleResponse);
401
404
  },
405
+ getExportAvailability: () => {
406
+ return getClient().GET("/api/export/availability").then(handleResponse);
407
+ },
402
408
  exportAsHTML: async (request) => {
403
409
  if (
404
410
  process.env.NODE_ENV === "development" ||
@@ -412,7 +418,7 @@ export function createNetworkRequests(): EditRequests & RunRequests {
412
418
  parseAs: "text",
413
419
  params: getParams(),
414
420
  })
415
- .then(handleResponse);
421
+ .then(handleExportResponse);
416
422
  },
417
423
  exportAsMarkdown: async (request) => {
418
424
  return getClient()
@@ -421,7 +427,7 @@ export function createNetworkRequests(): EditRequests & RunRequests {
421
427
  parseAs: "text",
422
428
  params: getParams(),
423
429
  })
424
- .then(handleResponse);
430
+ .then(handleExportResponse);
425
431
  },
426
432
  exportAsIPYNB: async (request) => {
427
433
  return getClient()
@@ -430,7 +436,7 @@ export function createNetworkRequests(): EditRequests & RunRequests {
430
436
  parseAs: "text",
431
437
  params: getParams(),
432
438
  })
433
- .then(handleResponse);
439
+ .then(handleExportResponse);
434
440
  },
435
441
  exportAsPDF: async (request) => {
436
442
  return getClient()
@@ -439,7 +445,7 @@ export function createNetworkRequests(): EditRequests & RunRequests {
439
445
  parseAs: "blob",
440
446
  params: getParams(),
441
447
  })
442
- .then(handleResponse);
448
+ .then(handleExportResponse);
443
449
  },
444
450
  autoExportAsHTML: async (request) => {
445
451
  return getClient()
@@ -489,12 +495,12 @@ export function createNetworkRequests(): EditRequests & RunRequests {
489
495
  },
490
496
  getPackageList: async () => {
491
497
  // If the sidebar is already open, it may try to load before the session has been initialized
492
- await waitForConnectionOpen();
498
+ await waitForConnectionOpenIfNotebook();
493
499
  return getClient().GET("/api/packages/list").then(handleResponse);
494
500
  },
495
501
  getDependencyTree: async () => {
496
502
  // If the sidebar is already open, it may try to load before the session has been initialized
497
- await waitForConnectionOpen();
503
+ await waitForConnectionOpenIfNotebook();
498
504
  return getClient().GET("/api/packages/tree").then(handleResponse);
499
505
  },
500
506
  listSecretKeys: async (request) => {
@@ -82,6 +82,7 @@ export function createStaticRequests(): EditRequests & RunRequests {
82
82
  getWorkspaceFiles: throwNotInEditMode,
83
83
  getRunningNotebooks: throwNotInEditMode,
84
84
  shutdownSession: throwNotInEditMode,
85
+ getExportAvailability: throwNotInEditMode,
85
86
  exportAsHTML: throwNotInEditMode,
86
87
  exportAsIPYNB: throwNotInEditMode,
87
88
  exportAsMarkdown: throwNotInEditMode,
@@ -63,6 +63,7 @@ export function createErrorToastingRequests(
63
63
  getWorkspaceFiles: "Failed to get workspace files",
64
64
  getRunningNotebooks: "Failed to get running notebooks",
65
65
  shutdownSession: "Failed to shutdown session",
66
+ getExportAvailability: "", // No toast
66
67
  exportAsHTML: "Failed to export HTML",
67
68
  exportAsIPYNB: "Failed to export ipynb",
68
69
  exportAsMarkdown: "Failed to export Markdown",
@@ -17,12 +17,23 @@ export type CellConfig = schemas["CellConfig"];
17
17
  export type RuntimeState = schemas["CellNotification"]["status"];
18
18
  export type CodeCompletionRequest = schemas["CodeCompletionRequest"];
19
19
  export type DeleteCellRequest = schemas["DeleteCellRequest"];
20
+ export type AutoExportAsIPYNBRequest = schemas["AutoExportAsIPYNBRequest"];
21
+ export type AutoExportAsMarkdownRequest =
22
+ schemas["AutoExportAsMarkdownRequest"];
20
23
  export type ExportAsHTMLRequest = schemas["ExportAsHTMLRequest"];
21
24
  export type ExportAsMarkdownRequest = schemas["ExportAsMarkdownRequest"];
22
25
  export type ExportAsIPYNBRequest = schemas["ExportAsIPYNBRequest"];
23
26
  export type ExportAsScriptRequest = schemas["ExportAsScriptRequest"];
24
27
  export type ExportAsPDFRequest = schemas["ExportAsPDFRequest"];
28
+ export type ExportAvailabilityResponse = schemas["ExportAvailabilityResponse"];
25
29
  export type UpdateCellOutputsRequest = schemas["UpdateCellOutputsRequest"];
30
+
31
+ export interface ExportedFile<T extends BlobPart = BlobPart> {
32
+ contents: T;
33
+ filename: string;
34
+ mediaType: string;
35
+ }
36
+
26
37
  export type FileCopyRequest = schemas["FileCopyRequest"];
27
38
  export type FileCopyResponse = schemas["FileCopyResponse"];
28
39
  export type FileCreateRequest = schemas["FileCreateRequest"];
@@ -191,7 +202,9 @@ export interface EditRequests {
191
202
  request: FileMoveRequest,
192
203
  ) => Promise<FileMoveResponse>;
193
204
  sendUpdateFile: (request: FileUpdateRequest) => Promise<FileUpdateResponse>;
194
- sendFileDetails: (request: { path: string }) => Promise<FileDetailsResponse>;
205
+ sendFileDetails: (
206
+ request: FileDetailsRequest,
207
+ ) => Promise<FileDetailsResponse>;
195
208
  // Homepage requests
196
209
  openTutorial: (request: OpenTutorialRequest) => Promise<MarimoFile>;
197
210
  getRecentFiles: () => Promise<RecentFilesResponse>;
@@ -203,13 +216,18 @@ export interface EditRequests {
203
216
  request: ShutdownSessionRequest,
204
217
  ) => Promise<RunningNotebooksResponse>;
205
218
  // Export requests
206
- exportAsHTML: (request: ExportAsHTMLRequest) => Promise<string>;
207
- exportAsIPYNB: (request: ExportAsIPYNBRequest) => Promise<string>;
208
- exportAsMarkdown: (request: ExportAsMarkdownRequest) => Promise<string>;
209
- exportAsPDF: (request: ExportAsPDFRequest) => Promise<Blob>;
219
+ getExportAvailability: () => Promise<ExportAvailabilityResponse>;
220
+ exportAsHTML: (request: ExportAsHTMLRequest) => Promise<ExportedFile<string>>;
221
+ exportAsIPYNB: (
222
+ request: ExportAsIPYNBRequest,
223
+ ) => Promise<ExportedFile<string>>;
224
+ exportAsMarkdown: (
225
+ request: ExportAsMarkdownRequest,
226
+ ) => Promise<ExportedFile<string>>;
227
+ exportAsPDF: (request: ExportAsPDFRequest) => Promise<ExportedFile<Blob>>;
210
228
  autoExportAsHTML: (request: ExportAsHTMLRequest) => Promise<null>;
211
- autoExportAsMarkdown: (request: ExportAsMarkdownRequest) => Promise<null>;
212
- autoExportAsIPYNB: (request: ExportAsIPYNBRequest) => Promise<null>;
229
+ autoExportAsMarkdown: (request: AutoExportAsMarkdownRequest) => Promise<null>;
230
+ autoExportAsIPYNB: (request: AutoExportAsIPYNBRequest) => Promise<null>;
213
231
  updateCellOutputs: (request: UpdateCellOutputsRequest) => Promise<null>;
214
232
  // Package requests
215
233
  getPackageList: () => Promise<ListPackagesResponse>;
@@ -1,31 +1,22 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { downloadBlob } from "@/utils/download";
4
- import { Filenames } from "@/utils/filenames";
5
- import { Paths } from "@/utils/paths";
3
+ import { downloadExportedFile } from "@/utils/download";
6
4
  import { getRequestClient } from "../network/requests";
7
5
  import { VirtualFileTracker } from "./virtual-file-tracker";
8
6
 
9
7
  /**
10
8
  * Downloads the current notebook as an HTML file.
11
9
  */
12
- export async function downloadAsHTML(opts: {
13
- filename: string;
14
- includeCode: boolean;
15
- }) {
10
+ export async function downloadAsHTML(opts: { includeCode: boolean }) {
16
11
  const client = getRequestClient();
17
- const { filename, includeCode } = opts;
18
- const html = await client.exportAsHTML({
12
+ const { includeCode } = opts;
13
+ const exportedFile = await client.exportAsHTML({
19
14
  download: true,
20
15
  includeCode: includeCode,
21
16
  files: VirtualFileTracker.INSTANCE.filenames(),
22
17
  });
23
- const filenameWithoutPath = Paths.basename(filename) ?? "notebook.py";
24
18
 
25
- downloadBlob(
26
- new Blob([html], { type: "text/html" }),
27
- Filenames.toHTML(filenameWithoutPath),
28
- );
19
+ downloadExportedFile(exportedFile);
29
20
  }
30
21
 
31
22
  function updateAssetUrl(existingUrl: string, assetBaseUrl: string) {
@@ -19,6 +19,7 @@ import type {
19
19
  EnvironmentInfo,
20
20
  ExportAsHTMLRequest,
21
21
  ExportAsMarkdownRequest,
22
+ ExportedFile,
22
23
  FileCopyResponse,
23
24
  FileCreateResponse,
24
25
  FileDeleteResponse,
@@ -520,7 +521,7 @@ export class PyodideBridge implements RunRequests, EditRequests {
520
521
  functionName: "export_html",
521
522
  payload: request,
522
523
  });
523
- return response as string;
524
+ return response as ExportedFile<string>;
524
525
  };
525
526
 
526
527
  exportAsMarkdown: EditRequests["exportAsMarkdown"] = async (
@@ -530,7 +531,7 @@ export class PyodideBridge implements RunRequests, EditRequests {
530
531
  functionName: "export_markdown",
531
532
  payload: request,
532
533
  });
533
- return response as string;
534
+ return response as ExportedFile<string>;
534
535
  };
535
536
 
536
537
  previewDatasetColumn: EditRequests["previewDatasetColumn"] = async (
@@ -642,6 +643,7 @@ export class PyodideBridge implements RunRequests, EditRequests {
642
643
  getWorkspaceFiles = throwNotImplemented;
643
644
  getRunningNotebooks = throwNotImplemented;
644
645
  shutdownSession = throwNotImplemented;
646
+ getExportAvailability = throwNotImplemented;
645
647
  exportAsIPYNB = throwNotImplemented;
646
648
  exportAsPDF = throwNotImplemented;
647
649
  autoExportAsHTML = throwNotImplemented;
@@ -12,6 +12,7 @@ import type {
12
12
  EnvironmentInfo,
13
13
  ExportAsHTMLRequest,
14
14
  ExportAsMarkdownRequest,
15
+ ExportedFile,
15
16
  FileCopyRequest,
16
17
  FileCopyResponse,
17
18
  FileCreateRequest,
@@ -96,8 +97,10 @@ export interface RawBridge {
96
97
  load_packages(request: string): Promise<string>;
97
98
  read_file(request: string): Promise<string>;
98
99
  set_interrupt_buffer(request: Uint8Array): Promise<string>;
99
- export_html(request: ExportAsHTMLRequest): Promise<string>;
100
- export_markdown(request: ExportAsMarkdownRequest): Promise<string>;
100
+ export_html(request: ExportAsHTMLRequest): Promise<ExportedFile<string>>;
101
+ export_markdown(
102
+ request: ExportAsMarkdownRequest,
103
+ ): Promise<ExportedFile<string>>;
101
104
  }
102
105
 
103
106
  export type BridgePayload<T extends keyof RawBridge> = T extends keyof RawBridge
@@ -148,7 +148,7 @@ export const FormWrapper = <T,>({
148
148
  if (evt.key === "Enter" && (evt.ctrlKey || evt.metaKey)) {
149
149
  evt.preventDefault();
150
150
  evt.stopPropagation();
151
- setValue(newValue);
151
+ formDiv.current?.requestSubmit();
152
152
  }
153
153
  }}
154
154
  >
@@ -0,0 +1,162 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import { FormWrapper } from "../FormPlugin";
6
+
7
+ describe("FormPlugin", () => {
8
+ it("submits the form on Ctrl+Enter and calls setValue", async () => {
9
+ const setValue = vi.fn();
10
+ const validate = vi.fn().mockResolvedValue(null);
11
+
12
+ render(
13
+ <FormWrapper
14
+ currentValue="old-val"
15
+ newValue="new-val"
16
+ setValue={setValue}
17
+ validate={validate}
18
+ shouldValidate={false}
19
+ clearOnSubmit={false}
20
+ submitButtonLabel="Submit"
21
+ bordered={true}
22
+ loading={false}
23
+ submitButtonDisabled={false}
24
+ showClearButton={false}
25
+ clearButtonLabel="Clear"
26
+ label={null}
27
+ >
28
+ <input data-testid="input" />
29
+ </FormWrapper>,
30
+ );
31
+
32
+ const input = screen.getByTestId("input");
33
+
34
+ // Simulate Ctrl+Enter on input
35
+ fireEvent.keyDown(input, { key: "Enter", ctrlKey: true });
36
+
37
+ await waitFor(() => {
38
+ expect(setValue).toHaveBeenCalledWith("new-val");
39
+ });
40
+ });
41
+
42
+ it("submits the form on Ctrl+Enter and runs validation when shouldValidate is true", async () => {
43
+ const setValue = vi.fn();
44
+ const validate = vi.fn().mockResolvedValue("Validation failed message");
45
+
46
+ render(
47
+ <FormWrapper
48
+ currentValue="old-val"
49
+ newValue="new-val"
50
+ setValue={setValue}
51
+ validate={validate}
52
+ shouldValidate={true}
53
+ clearOnSubmit={false}
54
+ submitButtonLabel="Submit"
55
+ bordered={true}
56
+ loading={false}
57
+ submitButtonDisabled={false}
58
+ showClearButton={false}
59
+ clearButtonLabel="Clear"
60
+ label={null}
61
+ >
62
+ <input data-testid="input" />
63
+ </FormWrapper>,
64
+ );
65
+
66
+ const input = screen.getByTestId("input");
67
+
68
+ // Simulate Ctrl+Enter on input
69
+ fireEvent.keyDown(input, { key: "Enter", ctrlKey: true });
70
+
71
+ await waitFor(() => {
72
+ expect(validate).toHaveBeenCalledWith({ value: "new-val" });
73
+ // should not submit (setValue not called) due to validation failure
74
+ expect(setValue).not.toHaveBeenCalled();
75
+ // error message displayed
76
+ expect(screen.getByText("Validation failed message")).toBeInTheDocument();
77
+ });
78
+ });
79
+
80
+ it("clears inputs on submission when clearOnSubmit is true", async () => {
81
+ const setValue = vi.fn();
82
+ const validate = vi.fn().mockResolvedValue(null);
83
+
84
+ let mockUIElement: (HTMLElement & { reset?: () => void }) | null = null;
85
+ const mockReset = vi.fn();
86
+
87
+ render(
88
+ <FormWrapper
89
+ currentValue="old-val"
90
+ newValue="new-val"
91
+ setValue={setValue}
92
+ validate={validate}
93
+ shouldValidate={false}
94
+ clearOnSubmit={true}
95
+ submitButtonLabel="Submit"
96
+ bordered={true}
97
+ loading={false}
98
+ submitButtonDisabled={false}
99
+ showClearButton={false}
100
+ clearButtonLabel="Clear"
101
+ label={null}
102
+ >
103
+ <div
104
+ ref={(el) => {
105
+ if (el && !mockUIElement) {
106
+ mockUIElement = document.createElement("marimo-ui-element");
107
+ mockUIElement.setAttribute("data-testid", "mock-ui-element");
108
+ mockUIElement.reset = mockReset;
109
+ const inputEl = document.createElement("input");
110
+ inputEl.type = "text";
111
+ mockUIElement.appendChild(inputEl);
112
+ el.appendChild(mockUIElement);
113
+ }
114
+ }}
115
+ />
116
+ </FormWrapper>,
117
+ );
118
+
119
+ const input = screen.getByRole("textbox");
120
+
121
+ // Simulate Ctrl+Enter on input
122
+ fireEvent.keyDown(input, { key: "Enter", ctrlKey: true });
123
+
124
+ await waitFor(() => {
125
+ expect(setValue).toHaveBeenCalledWith("new-val");
126
+ expect(mockReset).toHaveBeenCalled();
127
+ });
128
+ });
129
+
130
+ it("does not submit on Shift+Enter", async () => {
131
+ const setValue = vi.fn();
132
+ const validate = vi.fn().mockResolvedValue(null);
133
+
134
+ render(
135
+ <FormWrapper
136
+ currentValue="old-val"
137
+ newValue="new-val"
138
+ setValue={setValue}
139
+ validate={validate}
140
+ shouldValidate={false}
141
+ clearOnSubmit={false}
142
+ submitButtonLabel="Submit"
143
+ bordered={true}
144
+ loading={false}
145
+ submitButtonDisabled={false}
146
+ showClearButton={false}
147
+ clearButtonLabel="Clear"
148
+ label={null}
149
+ >
150
+ <input data-testid="input" />
151
+ </FormWrapper>,
152
+ );
153
+
154
+ const input = screen.getByTestId("input");
155
+
156
+ // Simulate Shift+Enter on input
157
+ fireEvent.keyDown(input, { key: "Enter", shiftKey: true });
158
+
159
+ expect(setValue).not.toHaveBeenCalled();
160
+ expect(validate).not.toHaveBeenCalled();
161
+ });
162
+ });
@@ -8,6 +8,7 @@ import {
8
8
  downloadAsPDF,
9
9
  downloadByURL,
10
10
  downloadCellOutputAsImage,
11
+ downloadExportedFile,
11
12
  downloadHTMLAsImage,
12
13
  getImageDataUrlForCell,
13
14
  withLoadingToast,
@@ -195,7 +196,6 @@ describe("downloadAsPDF", () => {
195
196
 
196
197
  await expect(
197
198
  downloadAsPDF({
198
- filename: "path/to/notebook.py",
199
199
  webpdf: false,
200
200
  preset: "slides",
201
201
  }),
@@ -572,6 +572,7 @@ describe("downloadByURL", () => {
572
572
 
573
573
  afterEach(() => {
574
574
  vi.restoreAllMocks();
575
+ vi.unstubAllGlobals();
575
576
  });
576
577
 
577
578
  it("should create anchor, set attributes, click, and remove", () => {
@@ -583,4 +584,26 @@ describe("downloadByURL", () => {
583
584
  expect(mockAnchor.click).toHaveBeenCalled();
584
585
  expect(mockAnchor.remove).toHaveBeenCalled();
585
586
  });
587
+
588
+ it("downloads an exported file with its returned metadata", async () => {
589
+ let downloadedBlob: Blob | undefined;
590
+ vi.stubGlobal("URL", {
591
+ createObjectURL: vi.fn((blob: Blob) => {
592
+ downloadedBlob = blob;
593
+ return "blob:export";
594
+ }),
595
+ revokeObjectURL: vi.fn(),
596
+ });
597
+
598
+ downloadExportedFile({
599
+ contents: "notebook",
600
+ filename: "résumé.qmd",
601
+ mediaType: "text/plain; charset=utf-8",
602
+ });
603
+
604
+ expect(downloadedBlob?.type).toBe("text/plain; charset=utf-8");
605
+ expect(await downloadedBlob?.text()).toBe("notebook");
606
+ expect(mockAnchor.download).toBe("résumé.qmd");
607
+ expect(URL.revokeObjectURL).toHaveBeenCalledWith("blob:export");
608
+ });
586
609
  });
@@ -4,8 +4,8 @@ import React from "react";
4
4
  import { toast } from "@/components/ui/use-toast";
5
5
  import { type CellId, CellOutputId } from "@/core/cells/ids";
6
6
  import { getRequestClient } from "@/core/network/requests";
7
+ import type { ExportedFile } from "@/core/network/types";
7
8
  import { Filenames } from "@/utils/filenames";
8
- import { Paths } from "@/utils/paths";
9
9
  import { prettyError } from "./errors";
10
10
  import { toPng } from "./html-to-image";
11
11
  import { captureExternalIframes } from "./iframe";
@@ -191,6 +191,14 @@ export function downloadBlob(blob: Blob, filename: string) {
191
191
  URL.revokeObjectURL(url);
192
192
  }
193
193
 
194
+ export function downloadExportedFile({
195
+ contents,
196
+ filename,
197
+ mediaType,
198
+ }: ExportedFile) {
199
+ downloadBlob(new Blob([contents], { type: mediaType }), filename);
200
+ }
201
+
194
202
  export type PDFExportPreset = "document" | "slides";
195
203
 
196
204
  /**
@@ -200,7 +208,6 @@ export type PDFExportPreset = "document" | "slides";
200
208
  * Standard PDF requires Pandoc & TeX (~few GBs) but is of higher quality.
201
209
  */
202
210
  export async function downloadAsPDF(opts: {
203
- filename: string;
204
211
  webpdf: boolean;
205
212
  preset?: PDFExportPreset;
206
213
  includeInputs?: boolean;
@@ -210,7 +217,6 @@ export async function downloadAsPDF(opts: {
210
217
  }) {
211
218
  const client = getRequestClient();
212
219
  const {
213
- filename,
214
220
  webpdf,
215
221
  preset = "document",
216
222
  includeInputs = true,
@@ -220,7 +226,7 @@ export async function downloadAsPDF(opts: {
220
226
  } = opts;
221
227
 
222
228
  try {
223
- const pdfBlob = await client.exportAsPDF({
229
+ const exportedFile = await client.exportAsPDF({
224
230
  webpdf,
225
231
  preset,
226
232
  includeInputs,
@@ -229,8 +235,7 @@ export async function downloadAsPDF(opts: {
229
235
  rasterServer,
230
236
  });
231
237
 
232
- const filenameWithoutPath = Paths.basename(filename);
233
- downloadBlob(pdfBlob, Filenames.toPDF(filenameWithoutPath));
238
+ downloadExportedFile(exportedFile);
234
239
  } catch (error) {
235
240
  toast({
236
241
  title: "Failed to download",
@@ -1 +0,0 @@
1
- import{s as x}from"./chunk-LvLJmgfZ.js";import{i as E,p as T,u as M}from"./useEvent-D91BmmQi.js";import{t as C}from"./react-Bj1aDYRI.js";import{Mi as S,Ni as f,Tn as H,at as j,kn as N,p as I,vt as L}from"./cells-Cf7bHHq3.js";import{t as g}from"./compiler-runtime-B3qBwwSJ.js";import{g as w}from"./useEventListener-B_w3A283.js";import{c as k}from"./utils-Z80SV4_d.js";import{t as V}from"./jsx-runtime-Blw4afVn.js";import{t as $}from"./tooltip-CFX4fYHU.js";import{t as A}from"./copy-icon-C2pG0ZNK.js";import{t as F}from"./usePress-5hBv4sjD.js";import{n as O}from"./useDebounce-JDKr83KO.js";import{t as _}from"./useRunCells-CSSF_mJ0.js";var W=g(),m=x(C(),1),l=x(V(),1);const q=e=>{let t=(0,W.c)(16),r,n,i;t[0]===e?(r=t[1],n=t[2],i=t[3]):({href:n,children:r,...i}=e,t[0]=e,t[1]=r,t[2]=n,t[3]=i);let a=(0,m.useRef)(null),o;t[4]===n?o=t[5]:(o=()=>{let h=new URL(globalThis.location.href);h.hash=n,globalThis.history.pushState({},"",h.toString()),globalThis.dispatchEvent(new HashChangeEvent("hashchange"));let R=n.slice(1),v=document.getElementById(R);v&&v.scrollIntoView({behavior:"smooth",block:"start"})},t[4]=n,t[5]=o);let s=o,c;t[6]===s?c=t[7]:(c={onPress:()=>{s()}},t[6]=s,t[7]=c);let{pressProps:d}=F(c),u;t[8]===s?u=t[9]:(u=h=>{h.preventDefault(),s()},t[8]=s,t[9]=u);let b=u,p;return t[10]!==r||t[11]!==b||t[12]!==n||t[13]!==d||t[14]!==i?(p=(0,l.jsx)("a",{ref:a,href:n,...d,onClick:b,...i,children:r}),t[10]=r,t[11]=b,t[12]=n,t[13]=d,t[14]=i,t[15]=p):p=t[15],p};async function z(e){let t=I().inOrderIds.at(0);if(t)try{let r=await H.request({document:e,cellId:t});if(!r||r.options.length===0)return;let n=e.split(".").pop()??e,i=r.options[0],a=r.options.find(o=>o.name===n)??i;a!=null&&a.completion_info&&E.set(N,{documentation:a.completion_info})}catch(r){w.debug(`Doc lookup failed for "${e}"`,r)}}var P=g();const D=e=>{let t=(0,P.c)(8),{qualifiedName:r,children:n}=e,i;t[0]===r?i=t[1]:(i=()=>{z(r)},t[0]=r,t[1]=i);let a=O(i,100),o;t[2]===a?o=t[3]:(o=()=>a.cancel(),t[2]=a,t[3]=o);let s;return t[4]!==n||t[5]!==a||t[6]!==o?(s=(0,l.jsx)("span",{onMouseEnter:a,onMouseLeave:o,children:n}),t[4]=n,t[5]=a,t[6]=o,t[7]=s):s=t[7],s};function B(e){if(typeof e!="object"||!e)return!1;let t=e;return!(t.trusted!==!0||t.notebookCode!==void 0&&typeof t.notebookCode!="string")}function U(){let e=window==null?void 0:window.__MARIMO_EXPORT_CONTEXT__;return B(e)?e:void 0}function y(){var e;return((e=U())==null?void 0:e.trusted)===!0}function X(){if(E.get(_)||y()||E.get(k))return!0;try{if(j()==="read")return!0}catch{}return!1}var G=T(e=>{let t=e(_),r=e(k);if(t||r||y())return!1;let n=!0;try{n=j()==="read"}catch{return!0}return!n});function J(){return M(G)}var K=g(),Y=e=>{if(e instanceof f.Element&&!/^[A-Za-z][\w-]*$/.test(e.name))return m.createElement(m.Fragment)},Z=(e,t)=>{if(t instanceof f.Element&&t.name==="body"){if((0,m.isValidElement)(e)&&"props"in e){let r=e.props.children;return(0,l.jsx)(l.Fragment,{children:r})}return}},Q=(e,t)=>{if(t instanceof f.Element&&t.name==="html"){if((0,m.isValidElement)(e)&&"props"in e){let r=e.props.children;return(0,l.jsx)(l.Fragment,{children:r})}return}},tt=e=>{if(e instanceof f.Element&&e.attribs&&e.name==="iframe"){let t=document.createElement("iframe");return Object.entries(e.attribs).forEach(([r,n])=>{r.startsWith('"')&&r.endsWith('"')&&(r=r.slice(1,-1)),t.setAttribute(r,n)}),(0,l.jsx)("div",{dangerouslySetInnerHTML:{__html:t.outerHTML}})}},et=e=>{if(e instanceof f.Element&&e.name==="script"){let t=e.attribs.src;if(!t)return;if(!X())return w.warn(`[RenderHTML] refusing <script src> in untrusted context: ${t}`),(0,l.jsx)(l.Fragment,{});if(![...document.querySelectorAll("script[src]")].some(r=>r.getAttribute("src")===t)){let r=document.createElement("script");r.src=t,document.head.append(r)}return(0,l.jsx)(l.Fragment,{})}},rt=(e,t)=>{if(t instanceof f.Element&&t.name==="a"){let r=t.attribs.href;if(r!=null&&r.startsWith("#")&&!r.startsWith("#code/")){let n=null;return(0,m.isValidElement)(e)&&"props"in e&&(n=e.props.children),(0,l.jsx)(q,{href:r,...t.attribs,children:n})}}},nt=(e,t,r)=>{var n,i;if(t instanceof f.Element&&t.name==="div"&&((i=(n=t.attribs)==null?void 0:n.class)!=null&&i.includes("codehilite")))return(0,l.jsx)(lt,{children:e},r)},it=(e,t,r)=>{var i;if(!(t instanceof f.Element)||t.name!=="img")return;let n=(i=t.attribs)==null?void 0:i.src;if(!(!n||!(0,m.isValidElement)(e))&&!/^data:/i.test(n))return(0,m.cloneElement)(e,{key:`${n}-${r}`})},at=(e,t,r)=>{var n;if(!(!(t instanceof f.Element)||t.name!=="input")&&((n=t.attribs)==null?void 0:n.type)==="checkbox"&&(0,m.isValidElement)(e))return(0,m.cloneElement)(e,{key:`checkbox-${r}-${"checked"in t.attribs}`})},ot=(e,t)=>{var r;if(t instanceof f.Element&&((r=t.attribs)!=null&&r["data-marimo-doc"])){let n=t.attribs["data-marimo-doc"];return(0,l.jsx)(D,{qualifiedName:n,children:e})}},st=(e,t)=>{var r,n;if(t instanceof f.Element&&((r=t.attribs)!=null&&r["data-tooltip"])){if((((n=t.name)==null?void 0:n.toLowerCase())??"").startsWith("marimo-"))return;let i=t.attribs["data-tooltip"];return(0,l.jsx)($,{content:i,children:e})}},lt=e=>{let t=(0,K.c)(3),{children:r}=e,n=(0,m.useRef)(null),i;t[0]===Symbol.for("react.memo_cache_sentinel")?(i=(0,l.jsx)("div",{className:"absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity",children:(0,l.jsx)(A,{tooltip:!1,className:"p-1",value:()=>{var s;let o=(s=n.current)==null?void 0:s.firstChild;return o&&o.textContent||""}})}),t[0]=i):i=t[0];let a;return t[1]===r?a=t[2]:(a=(0,l.jsxs)("div",{className:"relative group codehilite-wrapper",ref:n,children:[r,i]}),t[1]=r,t[2]=a),a};const ct=({html:e,additionalReplacements:t=[],alwaysSanitizeHtml:r=!0})=>(0,l.jsx)(mt,{html:e,alwaysSanitizeHtml:r,additionalReplacements:t});var mt=({html:e,additionalReplacements:t=[],alwaysSanitizeHtml:r})=>{let n=J();return ft({html:(0,m.useMemo)(()=>r||n?L(e):e,[e,r,n]),additionalReplacements:t})};function ft({html:e,additionalReplacements:t=[]}){let r=[Y,tt,et,...t],n=[nt,rt,ot,st,Z,Q],i=[it,at];return S(e,{replace:(a,o)=>{for(let s of r){let c=s(a,o);if(c)return c}return a},transform:(a,o,s)=>{let c=a;for(let d of n){let u=d(c,o,s);if(u){c=u;break}}for(let d of i){let u=d(c,o,s);u&&(c=u)}return c}})}export{y as n,ct as t};