@marimo-team/frontend 0.25.3-dev5 → 0.25.3-dev52

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-RQxLjC9P.js +1 -0
  2. package/dist/assets/{ConnectedDataExplorerComponent-t8fH_TNc.js → ConnectedDataExplorerComponent-D9UK8o9A.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-B4n9EnHB.js → ErrorBoundary-DzEMMFh0.js} +2 -2
  4. package/dist/assets/JsonOutput-9IONH7hu.js +11 -0
  5. package/dist/assets/{LazyAnyLanguageCodeMirror-CKcCbG2G.js → LazyAnyLanguageCodeMirror-BPYE76jR.js} +2 -2
  6. package/dist/assets/MarimoErrorOutput-BT8Qy5wh.js +10 -0
  7. package/dist/assets/{RenderHTML-9bMgbYmo.js → RenderHTML-PyMU3fBB.js} +1 -1
  8. package/dist/assets/{RunButton-CFQUNN5c.js → RunButton-2p2uCp9U.js} +1 -1
  9. package/dist/assets/{add-cell-with-ai-C_SruRWS.js → add-cell-with-ai-CEQ8jvcR.js} +7 -7
  10. package/dist/assets/{add-connection-dialog-CoE_PhuD.js → add-connection-dialog-D9fnw-j0.js} +2 -2
  11. package/dist/assets/{agent-panel-0b3k4QzJ.js → agent-panel-DkTYXm6j.js} +3 -3
  12. package/dist/assets/ai-model-dropdown-Cv9fEAp9.js +13 -0
  13. package/dist/assets/{alert-dialog-BaPGzeOK.js → alert-dialog-C-sWmKva.js} +1 -1
  14. package/dist/assets/any-language-editor-DUMe1UCk.js +1 -0
  15. package/dist/assets/{app-config-button-iXdCWvfC.js → app-config-button-BZzwY1BU.js} +1 -1
  16. package/dist/assets/arrays-0R2reF2y.js +1 -0
  17. package/dist/assets/{blob-iOBwGaP9.js → blob-DmUUV8Xf.js} +1 -1
  18. package/dist/assets/{button-COI-NGrx.js → button-D8gVKwe-.js} +1 -1
  19. package/dist/assets/{cache-panel-DTY1dqvi.js → cache-panel-Byolv2ht.js} +1 -1
  20. package/dist/assets/{capabilities-BJvf-MD4.js → capabilities-Pq1YNPlK.js} +1 -1
  21. package/dist/assets/{cell-editor-CR-tZNnc.js → cell-editor-C6xeN3x0.js} +6 -6
  22. package/dist/assets/cell-link-BNDZZ97N.js +1 -0
  23. package/dist/assets/{cells-rA_jsZCj.js → cells-0e2dpq3z.js} +56 -56
  24. package/dist/assets/{chat-display-luF7psRA.js → chat-display-D6TNcsmz.js} +1 -1
  25. package/dist/assets/chat-panel-CN45Q9wF.js +4 -0
  26. package/dist/assets/{chat-ui-nV_m4Btr.js → chat-ui-BGl1jPPe.js} +1 -1
  27. package/dist/assets/{circle-DXf-VWZG.js → circle-B6D_6D8w.js} +1 -1
  28. package/dist/assets/code-visibility-CV3S4xuf.js +1 -0
  29. package/dist/assets/coerce-DWjFeXOY.js +1 -0
  30. package/dist/assets/{column-preview-uosAAL4X.js → column-preview-D7sTOokc.js} +1 -1
  31. package/dist/assets/command-palette-CP5KmR-S.js +1 -0
  32. package/dist/assets/{common-lisZ0l6a.js → common-GvwCMUGF.js} +1 -1
  33. package/dist/assets/{components-CP_UoFtT.js → components-DrU8dOzy.js} +1 -1
  34. package/dist/assets/{components-CXfO-F47.js → components-lDm0qi2t.js} +1 -1
  35. package/dist/assets/{components-S7ZwOrvO.js → components-nnFZuekV.js} +1 -1
  36. package/dist/assets/config-CjQ4WJnJ.js +1 -0
  37. package/dist/assets/config-DGX7JYor.js +1 -0
  38. package/dist/assets/config-DzdxZb17.js +1 -0
  39. package/dist/assets/{copy-D8O_CYCG.js → copy-KlhazlhB.js} +1 -1
  40. package/dist/assets/{copy-icon-BIiRXh_3.js → copy-icon-8-a5vovc.js} +1 -1
  41. package/dist/assets/{createReducer-DgRODTW6.js → createReducer-HKXUbqyz.js} +1 -1
  42. package/dist/assets/{datasource-V0-52XOh.js → datasource-BpMPWJoj.js} +1 -1
  43. package/dist/assets/{dates-CfmdTL6B.js → dates-CQMwFko2.js} +1 -1
  44. package/dist/assets/{dependency-graph-panel-B0uP-_id.js → dependency-graph-panel-lyJ3aygI.js} +1 -1
  45. package/dist/assets/{dist-iaThVDkA.js → dist-jlhMTKo_.js} +1 -1
  46. package/dist/assets/{documentation-panel-DVl_ebWy.js → documentation-panel-CTUWNQQy.js} +1 -1
  47. package/dist/assets/{download-Bx55LMki.js → download-Csw35OD4.js} +1 -1
  48. package/dist/assets/{dropdown-menu-C1rnl-0o.js → dropdown-menu-DS6umAnH.js} +1 -1
  49. package/dist/assets/{edit-page-CGyjT-CQ.js → edit-page-Dwf3-IB6.js} +6 -6
  50. package/dist/assets/{error-banner-Cd69vvyX.js → error-banner-B5kzVf_0.js} +1 -1
  51. package/dist/assets/{error-entries-DOWpdGvI.js → error-entries-D3H6JH3A.js} +1 -1
  52. package/dist/assets/{error-panel-C-uxLOHF.js → error-panel-CgpCPUEO.js} +1 -1
  53. package/dist/assets/{errors-DCrsa1nH.js → errors-CNsgAw6m.js} +1 -1
  54. package/dist/assets/{es-Cqst59WX.js → es-N9SHpmG7.js} +1 -1
  55. package/dist/assets/feedback-button-B8idT199.js +1 -0
  56. package/dist/assets/{feedback-button-B99ThNJW.js → feedback-button-BEBY1q2a.js} +2 -2
  57. package/dist/assets/file-explorer-panel-MnNgzxxE.js +1 -0
  58. package/dist/assets/{file-name-input-ChnBsDE_.js → file-name-input-DrtIQ1VP.js} +1 -1
  59. package/dist/assets/{floating-outline-MYqQW4Vp.js → floating-outline-Dtc8YhnE.js} +1 -1
  60. package/dist/assets/{formats-yZ06h1Pl.js → formats-zylfwejq.js} +1 -1
  61. package/dist/assets/{formatting-BqrP-n49.js → formatting-uom1es4b.js} +1 -1
  62. package/dist/assets/{fullscreen-B9tHcfpV.js → fullscreen-BWTs8pYZ.js} +1 -1
  63. package/dist/assets/{gallery-page-DSL0J4md.js → gallery-page-DL4SSfun.js} +1 -1
  64. package/dist/assets/{ganttDiagram-EL5Y4UJY-2n7Ig5Jm.js → ganttDiagram-EL5Y4UJY-CYqkCfFG.js} +1 -1
  65. package/dist/assets/{glide-data-editor-B1MWlKeM.js → glide-data-editor-BTlU9F_s.js} +2 -2
  66. package/dist/assets/{glide-utils-CIrAxdjv.js → glide-utils-HFqhvWHI.js} +2 -2
  67. package/dist/assets/{home-page-B1M3X9Yf.js → home-page-BOmrrw3X.js} +3 -3
  68. package/dist/assets/{hooks-Bj0C0zrZ.js → hooks-DRNEKPFW.js} +1 -1
  69. package/dist/assets/{html-to-image-DK9Zl1lU.js → html-to-image-D_0rd3_B.js} +1 -1
  70. package/dist/assets/index-BdCbOYjS.js +30 -0
  71. package/dist/assets/index-evFWHKmM.css +2 -0
  72. package/dist/assets/{input-BoBxR50a.js → input-ByHt6aoV.js} +1 -1
  73. package/dist/assets/{kiosk-mode-BZECfg9k.js → kiosk-mode-BGmMSc7A.js} +1 -1
  74. package/dist/assets/{layout-DBuMeWh0.js → layout-DY2TXsTM.js} +5 -5
  75. package/dist/assets/linear-Bma8J3iM.js +1 -0
  76. package/dist/assets/links-CXcpNIM0.js +1 -0
  77. package/dist/assets/{logs-panel-y0XnfulJ.js → logs-panel-9f0amGf0.js} +1 -1
  78. package/dist/assets/{markdown-renderer-Q5AhbRU3.js → markdown-renderer-BRDCZeXA.js} +1 -1
  79. package/dist/assets/{menu-items-BrwnTgyK.js → menu-items-BgUyNUpu.js} +1 -1
  80. package/dist/assets/{mermaid-a3xYymTy.js → mermaid-B6mqMm5_.js} +2 -2
  81. package/dist/assets/{name-cell-input-BNOnF6-0.js → name-cell-input-13mN6CiO.js} +1 -1
  82. package/dist/assets/numbers-CsZVw3Dc.js +1 -0
  83. package/dist/assets/{objects-snlMn4yk.js → objects-rLPlQ3X_.js} +1 -1
  84. package/dist/assets/{once-Haz_uxEL.js → once-6KNTRW0u.js} +1 -1
  85. package/dist/assets/{outline-panel-Be7stWwv.js → outline-panel-DxC82nfD.js} +1 -1
  86. package/dist/assets/{packages-panel-9Qs1npCZ.js → packages-panel-Bi_mcFbl.js} +1 -1
  87. package/dist/assets/{panel-accordion-state-GeMEdCPw.js → panel-accordion-state-D_2i5aDC.js} +1 -1
  88. package/dist/assets/panels-C-ygF1Rv.js +107 -0
  89. package/dist/assets/precisionRound-BxlUs8G4.js +1 -0
  90. package/dist/assets/process-output-DlZH3EId.js +1 -0
  91. package/dist/assets/{quadrantDiagram-AXDQQJYC-yBhCDI7E.js → quadrantDiagram-AXDQQJYC-D9KH5Zge.js} +1 -1
  92. package/dist/assets/react-vega-BTNeboR2.js +189 -0
  93. package/dist/assets/{readonly-code-display-YULOXf4h.js → readonly-code-display-C8kpTYRT.js} +1 -1
  94. package/dist/assets/{readonly-python-code-CadgXgRu.js → readonly-python-code-XeDpkiXX.js} +1 -1
  95. package/dist/assets/{renderShortcut-BkVJQ_Zh.js → renderShortcut-H60uiHBp.js} +1 -1
  96. package/dist/assets/{reveal-component-B9FLjTg_.js → reveal-component-DgYlZOla.js} +37 -37
  97. package/dist/assets/run-page-C_A8W1eR.js +1 -0
  98. package/dist/assets/{runs-BLp5jezv.js → runs-6lJxKV1w.js} +1 -1
  99. package/dist/assets/{runtime-details-state-DuChzsA6.js → runtime-details-state-BeyT-Z-4.js} +1 -1
  100. package/dist/assets/{schemas-Dv42wLFq.js → schemas-ChRqMOTL.js} +1 -1
  101. package/dist/assets/{scratchpad-panel-DtB0A8xj.js → scratchpad-panel-Fb-8g4-S.js} +1 -1
  102. package/dist/assets/{scroll-to-bottom-button-1FyowpWV.js → scroll-to-bottom-button-CIkON2WJ.js} +1 -1
  103. package/dist/assets/{secrets-panel-Bctbtkrp.js → secrets-panel-CmLZsNfp.js} +1 -1
  104. package/dist/assets/{select-DTBn5eTb.js → select-3CVGlYrC.js} +1 -1
  105. package/dist/assets/{session-DqoWmb7X.js → session-B8Y-GJnS.js} +1 -1
  106. package/dist/assets/{session-panel-B6tA28BD.js → session-panel-Cl85FUT5.js} +1 -1
  107. package/dist/assets/{snippets-panel-CQ1dn3lA.js → snippets-panel-1hl_ey1w.js} +1 -1
  108. package/dist/assets/{spec-BfSBcO5B.js → spec-uCpVvaAR.js} +1 -1
  109. package/dist/assets/{state-uwRvJLb8.js → state-BY4_P6ko.js} +1 -1
  110. package/dist/assets/{state-BAJYRhmO.js → state-C0irhlOZ.js} +1 -1
  111. package/dist/assets/{state-BUsmcyaT.js → state-CbjD81T1.js} +3 -3
  112. package/dist/assets/{state-DxQnC_1I.js → state-Ck7FMD5A.js} +1 -1
  113. package/dist/assets/{state-Bfh_2SLl.js → state-ClkxCKxQ.js} +1 -1
  114. package/dist/assets/{state-C3oDoyFe.js → state-DaWgt-rn.js} +1 -1
  115. package/dist/assets/{strings-B2vv-ENv.js → strings-C25fhMR_.js} +1 -1
  116. package/dist/assets/{swiper-component-a07domrZ.js → swiper-component-CRGRy5_O.js} +1 -1
  117. package/dist/assets/{switch-CTaiAjrz.js → switch-1h011wlH.js} +1 -1
  118. package/dist/assets/{tabs-BuQlZFbt.js → tabs-CY8E4r9U.js} +1 -1
  119. package/dist/assets/{terminal-Ck_vK-34.js → terminal-D__Z_QZ0.js} +1 -1
  120. package/dist/assets/{time-MgToa8VM.js → time-DswSXBDk.js} +1 -1
  121. package/dist/assets/{toast-BGzLGnvw.js → toast-Gzxi-le_.js} +1 -1
  122. package/dist/assets/tooltip-Brya4Ghn.js +61 -0
  123. package/dist/assets/{tooltip-C9TuY18o.js → tooltip-CkGm8X7m.js} +1 -1
  124. package/dist/assets/tracing-DcFczEy0.js +1 -0
  125. package/dist/assets/{tracing-panel-dgnr2fIN.js → tracing-panel-EWvfWPFQ.js} +2 -2
  126. package/dist/assets/{tree-actions-DAd80MbP.js → tree-actions-Dof5VISm.js} +1 -1
  127. package/dist/assets/{use-restore-focus-BxEGwIbk.js → use-restore-focus-DdS7Tl0F.js} +1 -1
  128. package/dist/assets/{useAddCell-DGstJK0S.js → useAddCell-CqnFgwhm.js} +1 -1
  129. package/dist/assets/useAsyncData-DhhdOGGd.js +1 -0
  130. package/dist/assets/useBoolean-CMgj9hnq.js +1 -0
  131. package/dist/assets/{useCellActionButton-DY9RlJTb.js → useCellActionButton--Sq8PDBs.js} +1 -1
  132. package/dist/assets/{useDeleteCell-DFBGlSQk.js → useDeleteCell-DNTqIwU8.js} +1 -1
  133. package/dist/assets/{useDependencyPanelTab-DydxsE5d.js → useDependencyPanelTab-C4_EiuVH.js} +1 -1
  134. package/dist/assets/{useEventListener-D9cftcHj.js → useEventListener-CjDeLqFC.js} +1 -1
  135. package/dist/assets/{useIframeCapabilities-CwTkczVN.js → useIframeCapabilities-Czr9e4uG.js} +1 -1
  136. package/dist/assets/{useInstallPackage-iX1eufdl.js → useInstallPackage-DDjpV5RE.js} +1 -1
  137. package/dist/assets/{useInterval--x3s1VpI.js → useInterval-DJdQmXnq.js} +1 -1
  138. package/dist/assets/{useNotebookActions-CupIPSlg.js → useNotebookActions-C00MOhyT.js} +1 -1
  139. package/dist/assets/{useRunCells-BFjFX3hz.js → useRunCells-DqLsZnRn.js} +1 -1
  140. package/dist/assets/{useSplitCell-Tqv5pPtL.js → useSplitCell-Daxc689Z.js} +1 -1
  141. package/dist/assets/{useTheme-COhSHkCj.js → useTheme-DLtPc-39.js} +1 -1
  142. package/dist/assets/{utils-CaQYvazl.js → utils-DfElfBpz.js} +1 -1
  143. package/dist/assets/vega-component-D7JFthDD.js +1 -0
  144. package/dist/assets/{vega-embed-container-DJPNkHPb.js → vega-embed-container-DtArSJz1.js} +1 -1
  145. package/dist/assets/vega-loader.browser-Bu25TKax.js +6 -0
  146. package/dist/assets/{write-secret-modal-DzKAInL2.js → write-secret-modal-usRSi1bb.js} +1 -1
  147. package/dist/assets/{xychartDiagram-S5SC5T6Z-KbGzG6-u.js → xychartDiagram-S5SC5T6Z-CeRnBS5e.js} +1 -1
  148. package/dist/index.html +73 -73
  149. package/package.json +4 -4
  150. package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
  151. package/src/__tests__/host-imports.test.ts +1 -0
  152. package/src/__tests__/mount.test.ts +8 -8
  153. package/src/components/app-config/ai-config.tsx +5 -1
  154. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  155. package/src/components/chat/chat-panel.tsx +3 -7
  156. package/src/components/chat/chat-utils.ts +4 -8
  157. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  158. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  159. package/src/components/data-table/__tests__/export-actions.test.tsx +417 -6
  160. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  161. package/src/components/data-table/columns.tsx +2 -1
  162. package/src/components/data-table/context-menu.tsx +36 -20
  163. package/src/components/data-table/date-popover.tsx +6 -4
  164. package/src/components/data-table/export-actions.tsx +263 -161
  165. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
  166. package/src/components/data-table/schemas.ts +14 -36
  167. package/src/components/editor/Output.tsx +5 -1
  168. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  169. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
  170. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +186 -34
  171. package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
  172. package/src/components/editor/actions/pair-with-agent-modal.tsx +96 -36
  173. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  174. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  175. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  176. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  177. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  178. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +277 -78
  179. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +196 -41
  180. package/src/components/editor/connections/components.tsx +1 -1
  181. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  182. package/src/components/editor/controls/command-palette.tsx +22 -10
  183. package/src/components/editor/dynamic-favicon.tsx +21 -40
  184. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +139 -36
  185. package/src/components/editor/errors/send-error-report-button.tsx +10 -7
  186. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  187. package/src/components/editor/output/MarimoTracebackOutput.tsx +28 -22
  188. package/src/components/editor/output/console/ConsoleOutput.tsx +8 -18
  189. package/src/components/pages/home-page.tsx +4 -4
  190. package/src/components/slides/slide-cell-view.tsx +1 -1
  191. package/src/components/tracing/tracing.tsx +2 -2
  192. package/src/core/attachments/__tests__/toast.test.tsx +58 -0
  193. package/src/core/attachments/state.ts +5 -8
  194. package/src/core/attachments/toast.ts +15 -0
  195. package/src/core/cells/__tests__/error-report.test.ts +53 -3
  196. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  197. package/src/core/cells/__tests__/session.test.ts +18 -3
  198. package/src/core/cells/cells.ts +2 -1
  199. package/src/core/cells/error-report.ts +14 -6
  200. package/src/core/cells/pending-delete-service.ts +2 -6
  201. package/src/core/cells/session.ts +19 -1
  202. package/src/core/config/pair.ts +5 -3
  203. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  204. package/src/core/datasets/data-source-discovery.ts +4 -6
  205. package/src/core/edit-app.tsx +2 -2
  206. package/src/core/host/AGENTS.md +11 -2
  207. package/src/core/host/__tests__/attachments.test.ts +56 -0
  208. package/src/core/host/__tests__/document.test.ts +126 -0
  209. package/src/core/host/__tests__/events.test.ts +49 -0
  210. package/src/core/host/__tests__/fixtures.ts +41 -0
  211. package/src/core/host/__tests__/notebook-client.test.ts +496 -0
  212. package/src/core/host/connection.ts +81 -0
  213. package/src/core/host/events.ts +68 -0
  214. package/src/core/host/notebook-client.ts +316 -0
  215. package/src/core/host/resources/attachments.ts +19 -0
  216. package/src/core/host/resources/document.ts +101 -0
  217. package/src/core/hotkeys/actions.ts +2 -4
  218. package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
  219. package/src/core/islands/bootstrap.ts +0 -1
  220. package/src/core/network/connection.ts +4 -2
  221. package/src/core/run-app.tsx +2 -2
  222. package/src/core/runtime/__tests__/runtime.test.ts +5 -12
  223. package/src/core/runtime/runtime.ts +5 -5
  224. package/src/core/static/files.ts +2 -0
  225. package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -17
  226. package/src/core/websocket/__tests__/{useWebSocket.test.ts → createConnectionTransport.test.ts} +1 -1
  227. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +149 -0
  228. package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +168 -253
  229. package/src/core/websocket/connection-state.ts +70 -0
  230. package/src/core/websocket/state.ts +7 -0
  231. package/src/core/websocket/transports/__tests__/sse.test.ts +18 -4
  232. package/src/core/websocket/transports/create.ts +31 -0
  233. package/src/core/websocket/transports/sse.ts +14 -2
  234. package/src/core/websocket/transports/transport.ts +12 -23
  235. package/src/core/websocket/types.ts +9 -39
  236. package/src/core/websocket/useNotebookClient.ts +78 -0
  237. package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -233
  238. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  239. package/src/hooks/useAsyncData.ts +3 -3
  240. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  241. package/src/hooks/useElapsedTime.ts +3 -3
  242. package/src/hooks/useInputHistory.ts +4 -7
  243. package/src/hooks/useResizeObserver.ts +4 -7
  244. package/src/mount.tsx +3 -3
  245. package/src/plugins/core/registerReactComponent.tsx +1 -1
  246. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  247. package/src/plugins/impl/FormPlugin.tsx +1 -1
  248. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +84 -0
  249. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  250. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  251. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  252. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  253. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  254. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  255. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  256. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  257. package/src/plugins/impl/panel/PanelPlugin.tsx +15 -16
  258. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  259. package/src/plugins/impl/panel/utils.ts +17 -0
  260. package/src/plugins/impl/vega/make-selectable.ts +3 -6
  261. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  262. package/src/stories/editor.stories.tsx +24 -17
  263. package/src/utils/__tests__/arrays.test.ts +35 -0
  264. package/src/utils/__tests__/numbers.test.ts +48 -1
  265. package/src/utils/__tests__/objects.test.ts +21 -0
  266. package/src/utils/arrays.ts +10 -0
  267. package/src/utils/numbers.ts +28 -17
  268. package/src/utils/objects.ts +3 -7
  269. package/src/utils/once.ts +19 -19
  270. package/dist/assets/CellStatus-nZpuDkm7.js +0 -1
  271. package/dist/assets/JsonOutput-d-k8v_17.js +0 -11
  272. package/dist/assets/MarimoErrorOutput-CLYUxiKW.js +0 -10
  273. package/dist/assets/ai-model-dropdown-C6GHHEdR.js +0 -5
  274. package/dist/assets/any-language-editor-BLCAD3yx.js +0 -1
  275. package/dist/assets/arrays-BqUx5USx.js +0 -1
  276. package/dist/assets/cell-link-1CynTVGQ.js +0 -1
  277. package/dist/assets/chat-panel-DDDTnLvB.js +0 -4
  278. package/dist/assets/code-visibility-B-_GvwR5.js +0 -1
  279. package/dist/assets/coerce-CpE6kUdX.js +0 -1
  280. package/dist/assets/command-palette-BZ13RKDJ.js +0 -1
  281. package/dist/assets/config-DX8Y2WbJ.js +0 -1
  282. package/dist/assets/config-FzDtjF1-.js +0 -1
  283. package/dist/assets/config-VPSq73n8.js +0 -1
  284. package/dist/assets/feedback-button-DlqZkJwz.js +0 -1
  285. package/dist/assets/file-explorer-panel-D3CphidS.js +0 -1
  286. package/dist/assets/index-CXIE-QEG.css +0 -2
  287. package/dist/assets/index-DnIgeCyQ.js +0 -29
  288. package/dist/assets/linear-0eij_aAG.js +0 -1
  289. package/dist/assets/links-DkkL9DVa.js +0 -1
  290. package/dist/assets/numbers-Mk3-CcoU.js +0 -1
  291. package/dist/assets/panels-B9x935By.js +0 -4
  292. package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
  293. package/dist/assets/process-output-DNFKMODr.js +0 -1
  294. package/dist/assets/react-vega-C3rYGtP6.js +0 -189
  295. package/dist/assets/run-page-CjdGaic3.js +0 -1
  296. package/dist/assets/tooltip-mT3CGest.js +0 -61
  297. package/dist/assets/tracing-BARlJ_Dk.js +0 -1
  298. package/dist/assets/useAsyncData-C7aJJz0Y.js +0 -1
  299. package/dist/assets/useBoolean-BDD6_xQQ.js +0 -1
  300. package/dist/assets/vega-component-CnadTgoE.js +0 -1
  301. package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
  302. package/src/core/host/__tests__/protocol.test.ts +0 -214
  303. package/src/core/host/protocol.ts +0 -1943
  304. package/src/core/websocket/useWebSocket.tsx +0 -88
@@ -12,7 +12,7 @@ import { Logger } from "@/utils/Logger";
12
12
  import type { Cell } from "@tanstack/react-table";
13
13
  import { useAtomValue } from "jotai";
14
14
  import { CopyIcon, FilterIcon, SquareStack } from "lucide-react";
15
- import type { RefObject } from "react";
15
+ import { type RefObject, useRef, useState } from "react";
16
16
  import useEvent from "react-use-event-hook";
17
17
  import {
18
18
  ContextMenu,
@@ -123,8 +123,21 @@ export const DataTableContextMenu = <TData,>({
123
123
  tableRef: RefObject<HTMLTableSectionElement | null>;
124
124
  copyAllCells: () => void;
125
125
  }) => {
126
+ const [activeCell, setActiveCell] = useState<Cell<TData, unknown> | null>(
127
+ null,
128
+ );
129
+ const highlightedCell = useRef<Element | null>(null);
126
130
  const handleContextMenuChange = useEvent((open: boolean) => {
131
+ highlightedCell.current?.classList.remove("bg-(--green-4)");
132
+ highlightedCell.current = null;
133
+ if (!open) {
134
+ // Forget the cell so a later right-click outside any cell can't reuse it
135
+ contextMenuRef.current = null;
136
+ return;
137
+ }
138
+
127
139
  const cell = contextMenuRef.current;
140
+ setActiveCell(cell);
128
141
  if (!cell) {
129
142
  return;
130
143
  }
@@ -138,42 +151,45 @@ export const DataTableContextMenu = <TData,>({
138
151
  return;
139
152
  }
140
153
 
141
- if (open) {
142
- cellElement.classList.add("bg-(--green-4)");
143
- } else {
144
- cellElement.classList.remove("bg-(--green-4)");
145
- }
154
+ cellElement.classList.add("bg-(--green-4)");
155
+ highlightedCell.current = cellElement;
146
156
  });
147
157
 
148
158
  return (
149
- <ContextMenu onOpenChange={handleContextMenuChange}>
150
- <ContextMenuTrigger asChild={true}>{tableBody}</ContextMenuTrigger>
159
+ // Open via the trigger so a right-click while already open retargets the
160
+ // menu; Radix only reports open changes, so use it for closing.
161
+ <ContextMenu
162
+ onOpenChange={(open) => {
163
+ if (!open) {
164
+ handleContextMenuChange(false);
165
+ }
166
+ }}
167
+ >
168
+ <ContextMenuTrigger
169
+ asChild={true}
170
+ onContextMenu={() => handleContextMenuChange(true)}
171
+ >
172
+ {tableBody}
173
+ </ContextMenuTrigger>
151
174
  <ContextMenuPortal>
152
- <CellContextMenu
153
- cellRef={contextMenuRef}
154
- copySelectedCells={copyAllCells}
155
- />
175
+ {activeCell && (
176
+ <CellContextMenu cell={activeCell} copySelectedCells={copyAllCells} />
177
+ )}
156
178
  </ContextMenuPortal>
157
179
  </ContextMenu>
158
180
  );
159
181
  };
160
182
 
161
183
  export const CellContextMenu = <TData,>({
162
- cellRef,
184
+ cell,
163
185
  copySelectedCells,
164
186
  }: {
165
- cellRef: RefObject<Cell<TData, unknown> | null>;
187
+ cell: Cell<TData, unknown>;
166
188
  copySelectedCells: () => void;
167
189
  }) => {
168
190
  const selectedCells = useAtomValue(selectedCellsAtom);
169
191
  const multipleSelectedCells = selectedCells.size > 1;
170
192
 
171
- const cell = cellRef.current;
172
- if (!cell) {
173
- Logger.error("No cell found in context menu");
174
- return;
175
- }
176
-
177
193
  const table = cell.getContext().table;
178
194
  const displayedValue = cell.getValue();
179
195
  const rawValue =
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- import React from "react";
2
+ import React, { useMemo } from "react";
3
3
  import { useDateFormatter, useLocale } from "react-aria";
4
4
  import { Tooltip } from "@/components/ui/tooltip";
5
5
 
@@ -45,6 +45,7 @@ export const DatePopover: React.FC<DatePopoverProps> = ({
45
45
 
46
46
  const TimezoneDisplay = ({ date }: { date: Date }) => {
47
47
  const { locale } = useLocale();
48
+ // The system timezone can change without the locale changing.
48
49
  const localTimezone = new Intl.DateTimeFormat(locale).resolvedOptions()
49
50
  .timeZone;
50
51
  const hasSubSeconds = date.getUTCMilliseconds() !== 0;
@@ -116,9 +117,10 @@ const RelativeTime = ({ date }: { date: Date }) => {
116
117
  const { locale } = useLocale();
117
118
 
118
119
  // Initialize relative time formatter with current locale and "auto" numeric style
119
- const relativeTimeFormatter = new Intl.RelativeTimeFormat(locale, {
120
- numeric: "auto",
121
- });
120
+ const relativeTimeFormatter = useMemo(
121
+ () => new Intl.RelativeTimeFormat(locale, { numeric: "auto" }),
122
+ [locale],
123
+ );
122
124
 
123
125
  const currentTime = new Date();
124
126
  // Get difference in seconds between now and the input date
@@ -14,6 +14,7 @@ import {
14
14
  TableIcon,
15
15
  } from "lucide-react";
16
16
  import React from "react";
17
+ import useEvent from "react-use-event-hook";
17
18
  import { downloadSizeLimitAtom } from "./download-policy/atoms";
18
19
  import type {
19
20
  DownloadAsArgs,
@@ -22,6 +23,7 @@ import type {
22
23
  ExportMetadata,
23
24
  GetExportMetadata,
24
25
  } from "./schemas";
26
+ import { useAsyncData } from "@/hooks/useAsyncData";
25
27
  import { logNever } from "@/utils/assertNever";
26
28
  import { cn } from "@/utils/cn";
27
29
  import { copyToClipboard } from "@/utils/copy";
@@ -274,19 +276,130 @@ const failureTitleForAction = (action: ExportAction): string =>
274
276
  const failureDescription = (error: unknown): string =>
275
277
  typeof error === "string" ? error : prettyError(error);
276
278
 
279
+ const OBSOLETE_GEOMETRY_MESSAGE =
280
+ "The selected geometry is no longer available. Choose another geometry to export.";
281
+
282
+ type GeographicBlocker =
283
+ | "format-unavailable"
284
+ | "geometry-required"
285
+ | "choose-geometry"
286
+ | "obsolete-geometry"
287
+ | "missing-crs";
288
+
289
+ /**
290
+ * Find the first reason that a geographic export cannot start with the
291
+ * selected geometry. The dialog uses it to disable a format, and the retry
292
+ * after a package installation uses it to decide whether to export.
293
+ */
294
+ function geographicBlocker(
295
+ metadata: ExportMetadata,
296
+ format: GeographicFormat,
297
+ geometryColumn: string | null,
298
+ ): GeographicBlocker | null {
299
+ if (metadata.formats[format]?.available === false) {
300
+ return "format-unavailable";
301
+ }
302
+ const selected = metadata.geometry_columns.find(
303
+ (column) => column.name === geometryColumn,
304
+ );
305
+ if (geometryColumn !== null && !selected) {
306
+ return "obsolete-geometry";
307
+ }
308
+ if (metadata.geometry_columns.length === 0) {
309
+ return format === "geojson" ? "geometry-required" : null;
310
+ }
311
+ if (!selected) {
312
+ return "choose-geometry";
313
+ }
314
+ if (format === "geojson" && !selected.crs) {
315
+ return "missing-crs";
316
+ }
317
+ return null;
318
+ }
319
+
320
+ const geometryDisplayName = (name: string | null): string | null =>
321
+ name === "" ? "(unnamed geometry)" : name;
322
+
323
+ function geographicBlockerReason(
324
+ blocker: GeographicBlocker | null,
325
+ formatLabel: string,
326
+ geometryColumn: string | null,
327
+ ): string | null {
328
+ switch (blocker) {
329
+ case "choose-geometry":
330
+ return `Choose a primary geometry column to export ${formatLabel}.`;
331
+ case "obsolete-geometry":
332
+ return OBSOLETE_GEOMETRY_MESSAGE;
333
+ case "missing-crs":
334
+ return `Geometry column ${geometryDisplayName(geometryColumn)} needs a CRS for GeoJSON export. Declare its source CRS in Python before exporting.`;
335
+ case "format-unavailable":
336
+ case "geometry-required":
337
+ case null:
338
+ return null;
339
+ default:
340
+ logNever(blocker);
341
+ return null;
342
+ }
343
+ }
344
+
345
+ /**
346
+ * One open dialog for one table source. Closing the dialog or replacing the
347
+ * source creates a new context, and work started under an older context must
348
+ * not change the dialog or start an export.
349
+ */
350
+ interface ExportContext {
351
+ source: unknown;
352
+ }
353
+
277
354
  export const ExportActions: React.FC<ExportActionProps> = (props) => {
278
355
  const { getExportMetadata, metadataSource } = props;
279
356
  const [exportDialogOpen, setExportDialogOpen] = React.useState(false);
280
357
  const [failure, setFailure] = React.useState<ExportFailure | null>(null);
281
358
  const [settings, setSettings] =
282
359
  React.useState<ExportSettings>(DEFAULT_SETTINGS);
283
- const [metadata, setMetadata] = React.useState<ExportMetadata | null>(null);
284
- const [metadataLoading, setMetadataLoading] = React.useState(false);
285
- const [metadataError, setMetadataError] = React.useState<string | null>(null);
286
- const [metadataRefresh, setMetadataRefresh] = React.useState(0);
360
+ const [exportContext, setExportContext] = React.useState<ExportContext>(
361
+ () => ({ source: metadataSource }),
362
+ );
363
+ if (exportContext.source !== metadataSource) {
364
+ setExportContext({ source: metadataSource });
365
+ }
366
+ const mounted = React.useRef(false);
367
+ const isCurrentContext = useEvent(
368
+ (context: ExportContext) => mounted.current && context === exportContext,
369
+ );
370
+ const metadataRequest = useAsyncData(async () => {
371
+ if (!exportDialogOpen || !getExportMetadata) {
372
+ return null;
373
+ }
374
+ return { context: exportContext, metadata: await getExportMetadata({}) };
375
+ }, [exportDialogOpen, getExportMetadata, exportContext]);
376
+ // The hook keeps earlier data while it reloads and after a failure. Only a
377
+ // completed response for the current context can enable an export.
378
+ const metadata =
379
+ metadataRequest.status === "success" &&
380
+ metadataRequest.data?.context === exportContext
381
+ ? metadataRequest.data.metadata
382
+ : null;
383
+ const metadataError =
384
+ metadataRequest.status === "error"
385
+ ? failureDescription(metadataRequest.error)
386
+ : null;
287
387
  const [geometryColumn, setGeometryColumn] = React.useState<string | null>(
288
388
  null,
289
389
  );
390
+ const [reconciledMetadata, setReconciledMetadata] =
391
+ React.useState<ExportMetadata | null>(null);
392
+ if (metadata !== reconciledMetadata) {
393
+ setReconciledMetadata(metadata);
394
+ if (metadata) {
395
+ setGeometryColumn((current) =>
396
+ current !== null &&
397
+ metadata.geometry_columns.some((column) => column.name === current)
398
+ ? current
399
+ : metadata.default_geometry_column,
400
+ );
401
+ }
402
+ }
290
403
  const hasGeometry = !!metadata?.geometry_columns.length;
291
404
  const [expandedFormat, setExpandedFormat] = React.useState<
292
405
  ConfigurableFormat | GeographicFormat | null
@@ -294,43 +407,17 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
294
407
  const triggerRef = React.useRef<HTMLButtonElement>(null);
295
408
  const dialogRef = React.useRef<HTMLDivElement>(null);
296
409
  const latestActionId = React.useRef(0);
410
+ React.useLayoutEffect(() => {
411
+ mounted.current = true;
412
+ return () => {
413
+ mounted.current = false;
414
+ latestActionId.current += 1;
415
+ };
416
+ }, []);
297
417
  React.useEffect(() => {
298
418
  latestActionId.current += 1;
419
+ setFailure(null);
299
420
  }, [metadataSource]);
300
- React.useEffect(() => {
301
- if (!exportDialogOpen || !getExportMetadata) {
302
- return;
303
- }
304
- let active = true;
305
- setMetadata(null);
306
- setMetadataLoading(true);
307
- setMetadataError(null);
308
- void getExportMetadata({}).then(
309
- (result) => {
310
- if (!active) {
311
- return;
312
- }
313
- setMetadata(result);
314
- setGeometryColumn((current) =>
315
- current !== null &&
316
- result.geometry_columns.some((column) => column.name === current)
317
- ? current
318
- : result.default_geometry_column,
319
- );
320
- setMetadataLoading(false);
321
- },
322
- (error: unknown) => {
323
- if (!active) {
324
- return;
325
- }
326
- setMetadataError(failureDescription(error));
327
- setMetadataLoading(false);
328
- },
329
- );
330
- return () => {
331
- active = false;
332
- };
333
- }, [exportDialogOpen, getExportMetadata, metadataSource, metadataRefresh]);
334
421
  const policy = useAtomValue(downloadSizeLimitAtom);
335
422
  const overLimit = !!(
336
423
  policy &&
@@ -368,9 +455,8 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
368
455
  setFailure(null);
369
456
  setExpandedFormat(null);
370
457
  setSettings(DEFAULT_SETTINGS);
371
- setMetadata(null);
372
- setMetadataError(null);
373
458
  setGeometryColumn(null);
459
+ setExportContext({ source: metadataSource });
374
460
  }
375
461
  };
376
462
 
@@ -553,57 +639,67 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
553
639
  }
554
640
  };
555
641
 
556
- const retryAfterInstall = async (action: ExportAction) => {
557
- if (isGeographic(action.format) && getExportMetadata) {
642
+ // Package installation can finish after the dialog closes or the source
643
+ // changes, so the retry carries the context in which installation began.
644
+ const retryAfterInstall = useEvent(
645
+ async (action: ExportAction, context: ExportContext) => {
646
+ if (!isCurrentContext(context)) {
647
+ return;
648
+ }
649
+ if (!isGeographic(action.format) || !getExportMetadata) {
650
+ retryAction(action);
651
+ return;
652
+ }
558
653
  const actionId = latestActionId.current;
654
+ const isCurrent = () =>
655
+ isCurrentContext(context) && actionId === latestActionId.current;
559
656
  let result: ExportMetadata;
560
657
  try {
561
658
  result = await getExportMetadata({});
562
- } catch (error) {
563
- if (actionId === latestActionId.current) {
564
- setMetadataError(failureDescription(error));
659
+ } catch {
660
+ if (isCurrent()) {
661
+ metadataRequest.refetch();
565
662
  }
566
663
  return;
567
664
  }
568
- if (actionId !== latestActionId.current) {
569
- return;
570
- }
571
- setMetadata(result);
572
- setMetadataError(null);
573
- if (result.formats[action.format]?.available === false) {
574
- setFailure(null);
665
+ if (!isCurrent()) {
575
666
  return;
576
667
  }
577
- if (
578
- geometryColumn !== null &&
579
- !result.geometry_columns.some(
580
- (column) => column.name === geometryColumn,
581
- )
582
- ) {
583
- setGeometryColumn(result.default_geometry_column);
584
- setActionFailure(actionId, {
585
- kind: "error",
586
- title: failureTitleForAction(action),
587
- description:
588
- "The selected geometry is no longer available. Choose another geometry to export.",
589
- action,
590
- });
591
- return;
592
- }
593
- if (result.geometry_columns.length > 0 && geometryColumn === null) {
594
- return;
595
- }
596
- if (
597
- action.format === "geojson" &&
598
- !result.geometry_columns.find(
599
- (column) => column.name === geometryColumn,
600
- )?.crs
601
- ) {
602
- setFailure(null);
603
- return;
668
+ // Validation reads its own response, so a display refresh cannot
669
+ // start an export that this check rejected.
670
+ metadataRequest.refetch();
671
+ const blocker = geographicBlocker(result, action.format, geometryColumn);
672
+ switch (blocker) {
673
+ case null:
674
+ retryAction(action);
675
+ return;
676
+ case "obsolete-geometry":
677
+ setGeometryColumn(result.default_geometry_column);
678
+ setActionFailure(actionId, {
679
+ kind: "error",
680
+ title: failureTitleForAction(action),
681
+ description: OBSOLETE_GEOMETRY_MESSAGE,
682
+ action,
683
+ });
684
+ return;
685
+ case "choose-geometry":
686
+ return;
687
+ case "format-unavailable":
688
+ case "geometry-required":
689
+ case "missing-crs":
690
+ setFailure(null);
691
+ return;
692
+ default:
693
+ logNever(blocker);
604
694
  }
605
- }
606
- retryAction(action);
695
+ },
696
+ );
697
+
698
+ const createInstallRetry = (action: ExportAction) => {
699
+ const context = exportContext;
700
+ return () => {
701
+ void retryAfterInstall(action, context);
702
+ };
607
703
  };
608
704
 
609
705
  const toggleExpanded = (format: ExportFormat) => {
@@ -665,14 +761,15 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
665
761
  const expanded =
666
762
  configurableFormat !== null &&
667
763
  expandedFormat === configurableFormat;
764
+ const FormatHeader = configurableFormat ? "button" : "div";
668
765
  const panelId = `export-options-${option.format}`;
766
+ const descriptionId = `${panelId}-description`;
767
+ const summaryId = `${panelId}-summary`;
669
768
  const rowFailure =
670
769
  failure?.action.format === option.format ? failure : null;
671
770
  const summary =
672
771
  isGeographic(option.format) && hasGeometry
673
- ? geometryColumn === ""
674
- ? "(unnamed geometry)"
675
- : (geometryColumn ?? "Choose geometry")
772
+ ? (geometryDisplayName(geometryColumn) ?? "Choose geometry")
676
773
  : settingsSummary(settings, option.format);
677
774
  const selectedGeometryIndex =
678
775
  metadata?.geometry_columns.findIndex(
@@ -681,20 +778,14 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
681
778
  const eligibility = isGeographic(option.format)
682
779
  ? metadata?.formats[option.format]
683
780
  : undefined;
684
- const selectedGeometry =
685
- metadata?.geometry_columns[selectedGeometryIndex];
686
- const missingCrs =
687
- option.format === "geojson" &&
688
- selectedGeometry !== undefined &&
689
- !selectedGeometry.crs;
781
+ const blocker =
782
+ isGeographic(option.format) && metadata
783
+ ? geographicBlocker(metadata, option.format, geometryColumn)
784
+ : null;
690
785
  const geometryReason = metadataError
691
786
  ? `Could not load geometry options: ${metadataError}`
692
787
  : (eligibility?.reason ??
693
- (hasGeometry && geometryColumn === null
694
- ? `Choose a primary geometry column to export ${optionLabel}.`
695
- : missingCrs
696
- ? `Geometry column ${geometryColumn === "" ? "(unnamed geometry)" : geometryColumn} needs a CRS for GeoJSON export. Declare its source CRS in Python before exporting.`
697
- : null));
788
+ geographicBlockerReason(blocker, optionLabel, geometryColumn));
698
789
  const missingPackageFailure =
699
790
  rowFailure?.kind === "missing-packages" ? rowFailure : null;
700
791
  const missingPackages =
@@ -704,13 +795,7 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
704
795
  : []);
705
796
  const geographicUnavailable =
706
797
  isGeographic(option.format) &&
707
- ((!!getExportMetadata && !metadata) ||
708
- metadataLoading ||
709
- !!metadataError ||
710
- eligibility?.available === false ||
711
- missingCrs ||
712
- (!!metadata?.geometry_columns.length &&
713
- geometryColumn === null));
798
+ ((!!getExportMetadata && !metadata) || blocker !== null);
714
799
  const copyUnavailable =
715
800
  isGeographic(option.format) &&
716
801
  (geographicUnavailable || missingPackages.length > 0);
@@ -720,55 +805,78 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
720
805
  data-testid={`export-row-${option.format}`}
721
806
  className={cn(expanded && "bg-muted/40")}
722
807
  >
723
- <div className="grid min-h-[60px] grid-cols-[28px_minmax(0,1fr)_auto] items-center gap-x-1 px-2.5 py-2 hover:bg-accent/50">
724
- {configurableFormat ? (
725
- <Button
726
- type="button"
727
- variant="ghost"
728
- size="icon"
729
- aria-label={`${optionLabel} options`}
730
- aria-expanded={expanded}
731
- aria-controls={panelId}
732
- onClick={() => toggleExpanded(option.format)}
808
+ <div className="grid grid-cols-[minmax(0,1fr)_auto] items-center hover:bg-accent/50">
809
+ <FormatHeader
810
+ type={configurableFormat ? "button" : undefined}
811
+ className={cn(
812
+ "grid min-h-[60px] min-w-0 grid-cols-[28px_minmax(0,1fr)] items-center gap-x-1 py-2 pl-2.5 pr-1 text-left",
813
+ configurableFormat &&
814
+ "cursor-pointer focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring",
815
+ )}
816
+ aria-label={
817
+ configurableFormat ? `${optionLabel} options` : undefined
818
+ }
819
+ aria-describedby={
820
+ configurableFormat
821
+ ? [descriptionId, summary && summaryId]
822
+ .filter(Boolean)
823
+ .join(" ")
824
+ : undefined
825
+ }
826
+ aria-expanded={configurableFormat ? expanded : undefined}
827
+ aria-controls={configurableFormat ? panelId : undefined}
828
+ onClick={
829
+ configurableFormat
830
+ ? () => toggleExpanded(option.format)
831
+ : undefined
832
+ }
833
+ >
834
+ <span
835
+ aria-hidden={true}
836
+ className="inline-flex h-6 w-6 items-center justify-center"
733
837
  >
734
- <ChevronRightIcon
735
- className={cn(
736
- "h-4 w-4 text-muted-foreground transition-transform",
737
- expanded && "rotate-90",
738
- )}
838
+ {configurableFormat && (
839
+ <ChevronRightIcon
840
+ className={cn(
841
+ "h-4 w-4 text-muted-foreground transition-transform",
842
+ expanded && "rotate-90",
843
+ )}
844
+ />
845
+ )}
846
+ </span>
847
+ <span className="flex min-w-0 items-center gap-3 pl-1">
848
+ <option.icon
849
+ aria-hidden={true}
850
+ data-testid={`export-format-icon-${option.format}`}
851
+ className="h-4 w-4 shrink-0 text-muted-foreground"
739
852
  />
740
- </Button>
741
- ) : (
742
- <span aria-hidden={true} className="h-6 w-6" />
743
- )}
744
- <div className="flex min-w-0 items-center gap-3 pl-1">
745
- <option.icon
746
- aria-hidden={true}
747
- data-testid={`export-format-icon-${option.format}`}
748
- className="h-4 w-4 shrink-0 text-muted-foreground"
749
- />
750
- <div className="grid min-w-0 gap-1">
751
- <span className="flex min-w-0 items-baseline gap-x-2">
752
- <span className="text-sm font-medium leading-tight">
753
- {optionLabel}
754
- </span>
755
- {summary && (
756
- <span
757
- data-testid={`export-summary-${option.format}`}
758
- className="min-w-0 truncate text-xs text-muted-foreground"
759
- >
760
- {summary}
853
+ <span className="grid min-w-0 gap-1">
854
+ <span className="flex min-w-0 items-baseline gap-x-2">
855
+ <span className="text-sm font-medium leading-tight">
856
+ {optionLabel}
761
857
  </span>
762
- )}
763
- </span>
764
- <span className="text-xs text-muted-foreground leading-tight">
765
- {option.format === "parquet" && hasGeometry
766
- ? "Parquet with geometry and CRS metadata"
767
- : option.description}
858
+ {summary && (
859
+ <span
860
+ id={summaryId}
861
+ data-testid={`export-summary-${option.format}`}
862
+ className="min-w-0 truncate text-xs text-muted-foreground"
863
+ >
864
+ {summary}
865
+ </span>
866
+ )}
867
+ </span>
868
+ <span
869
+ id={descriptionId}
870
+ className="text-xs text-muted-foreground leading-tight"
871
+ >
872
+ {option.format === "parquet" && hasGeometry
873
+ ? "Parquet with geometry and CRS metadata"
874
+ : option.description}
875
+ </span>
768
876
  </span>
769
- </div>
770
- </div>
771
- <div className="flex items-center gap-0.5">
877
+ </span>
878
+ </FormatHeader>
879
+ <div className="flex items-center gap-0.5 pr-2.5">
772
880
  <Tooltip
773
881
  content={
774
882
  option.canDownload
@@ -888,9 +996,7 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
888
996
  type="button"
889
997
  size="xs"
890
998
  variant="outline"
891
- onClick={() =>
892
- setMetadataRefresh((current) => current + 1)
893
- }
999
+ onClick={metadataRequest.refetch}
894
1000
  >
895
1001
  Retry
896
1002
  </Button>
@@ -906,17 +1012,13 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
906
1012
  missingPackageFailure?.description ??
907
1013
  eligibility?.reason
908
1014
  }
909
- onInstall={() => {
910
- void retryAfterInstall(
911
- missingPackageFailure?.action ?? {
912
- destination: "download",
913
- format:
914
- option.format === "geojson"
915
- ? "geojson"
916
- : "parquet",
917
- },
918
- );
919
- }}
1015
+ onInstall={createInstallRetry(
1016
+ missingPackageFailure?.action ?? {
1017
+ destination: "download",
1018
+ format:
1019
+ option.format === "geojson" ? "geojson" : "parquet",
1020
+ },
1021
+ )}
920
1022
  className="items-start"
921
1023
  />
922
1024
  </div>