@marimo-team/frontend 0.25.3-dev4 → 0.25.3-dev42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/CellStatus-BpOBz-8m.js +1 -0
- package/dist/assets/{ConnectedDataExplorerComponent-t8fH_TNc.js → ConnectedDataExplorerComponent-D9UK8o9A.js} +1 -1
- package/dist/assets/{ErrorBoundary-Gl5nCA8C.js → ErrorBoundary-DmXl4Nre.js} +2 -2
- package/dist/assets/JsonOutput-BJraU9kp.js +11 -0
- package/dist/assets/{LazyAnyLanguageCodeMirror-CKcCbG2G.js → LazyAnyLanguageCodeMirror-BPYE76jR.js} +2 -2
- package/dist/assets/MarimoErrorOutput-DVjK8RgY.js +10 -0
- package/dist/assets/{RenderHTML-BbOqmHy3.js → RenderHTML-BFaztTR0.js} +1 -1
- package/dist/assets/{RunButton-CK3LhnP5.js → RunButton-B44xE_d5.js} +1 -1
- package/dist/assets/{add-cell-with-ai-8UBC0xwR.js → add-cell-with-ai-CkjslziO.js} +13 -13
- package/dist/assets/{add-connection-dialog-DXQnd6eB.js → add-connection-dialog-CWLLVKju.js} +2 -2
- package/dist/assets/{agent-panel-DdkVL-Kx.js → agent-panel-DYdQ_2pp.js} +5 -5
- package/dist/assets/ai-model-dropdown-F3mm17Wa.js +13 -0
- package/dist/assets/{alert-dialog-BaPGzeOK.js → alert-dialog-C-sWmKva.js} +1 -1
- package/dist/assets/any-language-editor-DUMe1UCk.js +1 -0
- package/dist/assets/{app-config-button-BKEG4x9u.js → app-config-button-D0uDs5nC.js} +1 -1
- package/dist/assets/arrays-0R2reF2y.js +1 -0
- package/dist/assets/{blob-D5nfNHOo.js → blob-BSfsdCNF.js} +1 -1
- package/dist/assets/{button-COI-NGrx.js → button-D8gVKwe-.js} +1 -1
- package/dist/assets/{cache-panel-DTY1dqvi.js → cache-panel-Byolv2ht.js} +1 -1
- package/dist/assets/{capabilities-BJvf-MD4.js → capabilities-Pq1YNPlK.js} +1 -1
- package/dist/assets/{cell-editor-32lVhib7.js → cell-editor-DzvvHs9g.js} +9 -9
- package/dist/assets/{cell-link-LSth_8hT.js → cell-link-DFPHORAO.js} +1 -1
- package/dist/assets/{cells-BRkCaSsh.js → cells-BdgzY0of.js} +31 -31
- package/dist/assets/{chat-display-Dnagy4Nv.js → chat-display-BrcwdhKJ.js} +1 -1
- package/dist/assets/{chat-panel-DN9vsbgS.js → chat-panel-C__3iSyI.js} +2 -2
- package/dist/assets/{chat-ui-Df8Vcj6n.js → chat-ui-NlBK3kK6.js} +1 -1
- package/dist/assets/{circle-DXf-VWZG.js → circle-B6D_6D8w.js} +1 -1
- package/dist/assets/code-visibility-Cs-Vv3jV.js +1 -0
- package/dist/assets/coerce-DWjFeXOY.js +1 -0
- package/dist/assets/{column-preview-CHK_DNrs.js → column-preview-ivm1b-GY.js} +1 -1
- package/dist/assets/command-palette-TgwswwvD.js +1 -0
- package/dist/assets/{common-Cu6CDbuw.js → common-BcEY1yDS.js} +1 -1
- package/dist/assets/{components-CP_UoFtT.js → components-DrU8dOzy.js} +1 -1
- package/dist/assets/{components-CXfO-F47.js → components-lDm0qi2t.js} +1 -1
- package/dist/assets/{components-CGw16R_N.js → components-lkCfd2vy.js} +1 -1
- package/dist/assets/config-BwHT0yGp.js +104 -0
- package/dist/assets/config-DGX7JYor.js +1 -0
- package/dist/assets/config-DXjnAwtL.js +1 -0
- package/dist/assets/{copy-D8O_CYCG.js → copy-KlhazlhB.js} +1 -1
- package/dist/assets/{copy-icon-BIiRXh_3.js → copy-icon-8-a5vovc.js} +1 -1
- package/dist/assets/{createReducer-DgRODTW6.js → createReducer-HKXUbqyz.js} +1 -1
- package/dist/assets/{datasource-Cq6x_5ML.js → datasource-dKk3JHSQ.js} +1 -1
- package/dist/assets/{dates-CfmdTL6B.js → dates-CQMwFko2.js} +1 -1
- package/dist/assets/{dependency-graph-panel--Kgtktcw.js → dependency-graph-panel-BaisS9Zt.js} +1 -1
- package/dist/assets/{dist-iaThVDkA.js → dist-jlhMTKo_.js} +1 -1
- package/dist/assets/{documentation-panel-BHmbeznw.js → documentation-panel-CpSUoM8r.js} +1 -1
- package/dist/assets/{download-DCJXy66H.js → download-RV4i1fWI.js} +1 -1
- package/dist/assets/{dropdown-menu-C1rnl-0o.js → dropdown-menu-DS6umAnH.js} +1 -1
- package/dist/assets/edit-page-C8RUVAaO.js +9 -0
- package/dist/assets/{error-banner-Cd69vvyX.js → error-banner-B5kzVf_0.js} +1 -1
- package/dist/assets/{error-entries-Dk3VZ-OK.js → error-entries-CJS0gOId.js} +1 -1
- package/dist/assets/{error-panel-BPRYmC5J.js → error-panel-BxCKpZtN.js} +1 -1
- package/dist/assets/{errors-DCrsa1nH.js → errors-CNsgAw6m.js} +1 -1
- package/dist/assets/{es-Cqst59WX.js → es-N9SHpmG7.js} +1 -1
- package/dist/assets/feedback-button-BLvwvndE.js +1 -0
- package/dist/assets/{feedback-button-Bik2-e33.js → feedback-button-DYjae3_b.js} +2 -2
- package/dist/assets/{file-explorer-panel-BmekFu7T.js → file-explorer-panel-BmjAJusg.js} +1 -1
- package/dist/assets/{file-name-input-Cdc-TVqO.js → file-name-input-BpfSzvEB.js} +1 -1
- package/dist/assets/{floating-outline-XD9FAV-f.js → floating-outline-CrWdiNQ2.js} +1 -1
- package/dist/assets/{formats-yZ06h1Pl.js → formats-zylfwejq.js} +1 -1
- package/dist/assets/{formatting-BqrP-n49.js → formatting-uom1es4b.js} +1 -1
- package/dist/assets/{fullscreen-B9tHcfpV.js → fullscreen-BWTs8pYZ.js} +1 -1
- package/dist/assets/{gallery-page-Dm6mGwBi.js → gallery-page-W_b-XWq6.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-2n7Ig5Jm.js → ganttDiagram-EL5Y4UJY-CYqkCfFG.js} +1 -1
- package/dist/assets/{glide-data-editor-J9CAb70d.js → glide-data-editor-C_bIL30J.js} +2 -2
- package/dist/assets/{glide-utils-CIrAxdjv.js → glide-utils-HFqhvWHI.js} +2 -2
- package/dist/assets/{home-page-s3LUizSn.js → home-page-D1teQDdF.js} +3 -3
- package/dist/assets/hooks-CZF85I2f.js +1 -0
- package/dist/assets/{html-to-image-DK9Zl1lU.js → html-to-image-D_0rd3_B.js} +1 -1
- package/dist/assets/index-4Nwpioic.js +30 -0
- package/dist/assets/index-ChfDZJzP.css +2 -0
- package/dist/assets/{input-BoBxR50a.js → input-ByHt6aoV.js} +1 -1
- package/dist/assets/{kiosk-mode-BZECfg9k.js → kiosk-mode-BGmMSc7A.js} +1 -1
- package/dist/assets/{layout-B_GMOySr.js → layout-ByZveRPN.js} +5 -5
- package/dist/assets/linear-Bma8J3iM.js +1 -0
- package/dist/assets/links-CXcpNIM0.js +1 -0
- package/dist/assets/{logs-panel-ABM16htT.js → logs-panel-DVSgfMgv.js} +1 -1
- package/dist/assets/{markdown-renderer-BG3sp2rs.js → markdown-renderer-DX2O5YiE.js} +1 -1
- package/dist/assets/{menu-items-BrwnTgyK.js → menu-items-BgUyNUpu.js} +1 -1
- package/dist/assets/{mermaid-a3xYymTy.js → mermaid-B6mqMm5_.js} +2 -2
- package/dist/assets/{name-cell-input-BgEBmqyL.js → name-cell-input-DL1i8Mj0.js} +1 -1
- package/dist/assets/numbers-CsZVw3Dc.js +1 -0
- package/dist/assets/{objects-snlMn4yk.js → objects-rLPlQ3X_.js} +1 -1
- package/dist/assets/{once-Haz_uxEL.js → once-6KNTRW0u.js} +1 -1
- package/dist/assets/{outline-panel-DCXQ3_F6.js → outline-panel-DPKPT2eg.js} +1 -1
- package/dist/assets/{packages-panel-BMGOmZkR.js → packages-panel-CZZwD9YV.js} +1 -1
- package/dist/assets/{panel-accordion-state-C1sdPp0m.js → panel-accordion-state-C7vvsYz1.js} +1 -1
- package/dist/assets/panels-BT2oosq8.js +4 -0
- package/dist/assets/precisionRound-BxlUs8G4.js +1 -0
- package/dist/assets/process-output-BKhybJBb.js +1 -0
- package/dist/assets/{quadrantDiagram-AXDQQJYC-yBhCDI7E.js → quadrantDiagram-AXDQQJYC-D9KH5Zge.js} +1 -1
- package/dist/assets/react-vega-BTNeboR2.js +189 -0
- package/dist/assets/{readonly-code-display-Do50PHI8.js → readonly-code-display-9QrSm_7D.js} +1 -1
- package/dist/assets/{readonly-python-code-DCSMIsTX.js → readonly-python-code-BLotL3XP.js} +1 -1
- package/dist/assets/{renderShortcut-BkVJQ_Zh.js → renderShortcut-H60uiHBp.js} +1 -1
- package/dist/assets/{reveal-component-CLE6vB1Z.js → reveal-component-2QaMbLNx.js} +3 -3
- package/dist/assets/{run-page-Depd6lEE.js → run-page-DyZ8JFKU.js} +1 -1
- package/dist/assets/{runs-BLp5jezv.js → runs-6lJxKV1w.js} +1 -1
- package/dist/assets/{runtime-details-state-CSPobEdR.js → runtime-details-state-B25uM-Ps.js} +1 -1
- package/dist/assets/{schemas-Dv42wLFq.js → schemas-ChRqMOTL.js} +1 -1
- package/dist/assets/{scratchpad-panel-lc6jmpjv.js → scratchpad-panel-Dt83MaaA.js} +1 -1
- package/dist/assets/{scroll-to-bottom-button-1FyowpWV.js → scroll-to-bottom-button-CIkON2WJ.js} +1 -1
- package/dist/assets/{secrets-panel-Bctbtkrp.js → secrets-panel-CmLZsNfp.js} +1 -1
- package/dist/assets/{select-DTBn5eTb.js → select-3CVGlYrC.js} +1 -1
- package/dist/assets/{session-DqoWmb7X.js → session-B8Y-GJnS.js} +1 -1
- package/dist/assets/session-panel-xVWVwa1U.js +1 -0
- package/dist/assets/{snippets-panel-Bh1oTw4m.js → snippets-panel-IxIxfxNc.js} +1 -1
- package/dist/assets/{spec-BfSBcO5B.js → spec-uCpVvaAR.js} +1 -1
- package/dist/assets/state-BHuYDaxs.js +3 -0
- package/dist/assets/{state-uwRvJLb8.js → state-BY4_P6ko.js} +1 -1
- package/dist/assets/{state-BAJYRhmO.js → state-C0irhlOZ.js} +1 -1
- package/dist/assets/{state-XsPO8hNi.js → state-CVgqwwCU.js} +1 -1
- package/dist/assets/{state-DxQnC_1I.js → state-Ck7FMD5A.js} +1 -1
- package/dist/assets/{state-C3oDoyFe.js → state-DaWgt-rn.js} +1 -1
- package/dist/assets/{strings-B2vv-ENv.js → strings-C25fhMR_.js} +1 -1
- package/dist/assets/{swiper-component-a07domrZ.js → swiper-component-CRGRy5_O.js} +1 -1
- package/dist/assets/{switch-CTaiAjrz.js → switch-1h011wlH.js} +1 -1
- package/dist/assets/{tabs-BuQlZFbt.js → tabs-CY8E4r9U.js} +1 -1
- package/dist/assets/{terminal-Ck_vK-34.js → terminal-CjvCrRrA.js} +3 -3
- package/dist/assets/{time-MgToa8VM.js → time-DswSXBDk.js} +1 -1
- package/dist/assets/{toast-BGzLGnvw.js → toast-Gzxi-le_.js} +1 -1
- package/dist/assets/tooltip-Brya4Ghn.js +61 -0
- package/dist/assets/{tooltip-C9TuY18o.js → tooltip-CkGm8X7m.js} +1 -1
- package/dist/assets/tracing-V5zejPsw.js +1 -0
- package/dist/assets/{tracing-panel-PeRXHD8_.js → tracing-panel-DLS3iwK6.js} +2 -2
- package/dist/assets/{tree-actions-GM5E_bUs.js → tree-actions-Be_mzC1C.js} +1 -1
- package/dist/assets/{use-restore-focus-BxEGwIbk.js → use-restore-focus-DdS7Tl0F.js} +1 -1
- package/dist/assets/{useAddCell-GR7eEQrT.js → useAddCell-DK79W1vA.js} +1 -1
- package/dist/assets/useAsyncData-DhhdOGGd.js +1 -0
- package/dist/assets/{useBoolean-BDD6_xQQ.js → useBoolean-j0qHaJxu.js} +1 -1
- package/dist/assets/{useCellActionButton-B-9eG5z_.js → useCellActionButton-Cw9Ob12j.js} +1 -1
- package/dist/assets/{useDeleteCell-2_bMClHd.js → useDeleteCell-BNqX6PPu.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-k3C3M18S.js → useDependencyPanelTab-GnTnqKJo.js} +1 -1
- package/dist/assets/{useEventListener-D9cftcHj.js → useEventListener-CjDeLqFC.js} +1 -1
- package/dist/assets/{useIframeCapabilities-CwTkczVN.js → useIframeCapabilities-Czr9e4uG.js} +1 -1
- package/dist/assets/{useInstallPackage-iX1eufdl.js → useInstallPackage-CVItDn1e.js} +1 -1
- package/dist/assets/{useInterval--x3s1VpI.js → useInterval-DJdQmXnq.js} +1 -1
- package/dist/assets/useNotebookActions-_qJ1Sjtj.js +1 -0
- package/dist/assets/{useRunCells-B2vUhREg.js → useRunCells-DP09DHT6.js} +1 -1
- package/dist/assets/{useSplitCell-DEb03htp.js → useSplitCell-IfumZ4sm.js} +1 -1
- package/dist/assets/{useTheme-COhSHkCj.js → useTheme-DLtPc-39.js} +1 -1
- package/dist/assets/{utils-CaQYvazl.js → utils-DfElfBpz.js} +1 -1
- package/dist/assets/vega-component-CMn031HF.js +1 -0
- package/dist/assets/{vega-embed-container-DJPNkHPb.js → vega-embed-container-DtArSJz1.js} +1 -1
- package/dist/assets/vega-loader.browser-Bu25TKax.js +6 -0
- package/dist/assets/{write-secret-modal-DzKAInL2.js → write-secret-modal-usRSi1bb.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-KbGzG6-u.js → xychartDiagram-S5SC5T6Z-CeRnBS5e.js} +1 -1
- package/dist/index.html +76 -77
- package/package.json +4 -4
- package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
- package/src/__tests__/host-imports.test.ts +1 -0
- package/src/__tests__/mount.test.ts +28 -8
- package/src/components/app-config/ai-config.tsx +5 -1
- package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
- package/src/components/data-table/__tests__/export-actions.test.tsx +329 -6
- package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
- package/src/components/data-table/columns.tsx +2 -1
- package/src/components/data-table/date-popover.tsx +6 -4
- package/src/components/data-table/export-actions.tsx +192 -116
- package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
- package/src/components/data-table/schemas.ts +14 -36
- package/src/components/editor/Output.tsx +9 -2
- package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +186 -34
- package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
- package/src/components/editor/actions/pair-with-agent-modal.tsx +96 -36
- package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
- package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
- package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +366 -0
- package/src/components/editor/chrome/wrapper/footer-item.tsx +32 -24
- package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +237 -0
- package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
- package/src/components/editor/connections/components.tsx +1 -1
- package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
- package/src/components/editor/controls/command-palette.tsx +22 -10
- package/src/components/editor/dynamic-favicon.tsx +21 -40
- package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +537 -0
- package/src/components/editor/errors/send-error-report-button.tsx +69 -0
- package/src/components/editor/output/MarimoErrorOutput.tsx +21 -5
- package/src/components/editor/output/MarimoTracebackOutput.tsx +36 -21
- package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
- package/src/components/editor/output/ansi-reduce.ts +6 -0
- package/src/components/pages/home-page.tsx +4 -4
- package/src/components/tracing/tracing.tsx +2 -2
- package/src/core/attachments/__tests__/toast.test.tsx +58 -0
- package/src/core/attachments/state.ts +18 -0
- package/src/core/attachments/toast.ts +15 -0
- package/src/core/cells/__tests__/cells.test.ts +52 -0
- package/src/core/cells/__tests__/error-report.test.ts +139 -0
- package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
- package/src/core/cells/__tests__/session.test.ts +50 -1
- package/src/core/cells/cells.ts +2 -1
- package/src/core/cells/error-report.ts +63 -0
- package/src/core/cells/pending-delete-service.ts +2 -6
- package/src/core/cells/session.ts +28 -1
- package/src/core/config/pair.ts +5 -3
- package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
- package/src/core/datasets/data-source-discovery.ts +4 -6
- package/src/core/edit-app.tsx +2 -2
- package/src/core/host/AGENTS.md +11 -2
- package/src/core/host/__tests__/attachments.test.ts +56 -0
- package/src/core/host/__tests__/document.test.ts +126 -0
- package/src/core/host/__tests__/events.test.ts +49 -0
- package/src/core/host/__tests__/fixtures.ts +41 -0
- package/src/core/host/__tests__/notebook-client.test.ts +433 -0
- package/src/core/host/connection.ts +81 -0
- package/src/core/host/events.ts +77 -0
- package/src/core/host/notebook-client.ts +323 -0
- package/src/core/host/resources/attachments.ts +19 -0
- package/src/core/host/resources/document.ts +101 -0
- package/src/core/hotkeys/actions.ts +2 -4
- package/src/core/network/connection.ts +4 -2
- package/src/core/run-app.tsx +2 -2
- package/src/core/runtime/__tests__/runtime.test.ts +55 -11
- package/src/core/runtime/runtime.ts +18 -5
- package/src/core/runtime/types.ts +2 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -17
- package/src/core/websocket/__tests__/{useWebSocket.test.ts → createConnectionTransport.test.ts} +1 -1
- package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +170 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +181 -65
- package/src/core/websocket/connection-state.ts +70 -0
- package/src/core/websocket/state.ts +7 -0
- package/src/core/websocket/transports/__tests__/sse.test.ts +1 -4
- package/src/core/websocket/transports/create.ts +31 -0
- package/src/core/websocket/transports/transport.ts +12 -23
- package/src/core/websocket/types.ts +9 -39
- package/src/core/websocket/useNotebookClient.ts +88 -0
- package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +59 -225
- package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
- package/src/hooks/useAsyncData.ts +3 -3
- package/src/hooks/useDataSourceDiscovery.ts +6 -4
- package/src/hooks/useElapsedTime.ts +3 -3
- package/src/mount.tsx +7 -3
- package/src/plugins/core/registerReactComponent.tsx +1 -1
- package/src/plugins/impl/DropdownPlugin.tsx +2 -6
- package/src/plugins/impl/FormPlugin.tsx +1 -1
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +84 -0
- package/src/plugins/impl/code/any-language-editor.tsx +2 -2
- package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
- package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
- package/src/plugins/impl/vega/make-selectable.ts +3 -6
- package/src/utils/__tests__/arrays.test.ts +35 -0
- package/src/utils/__tests__/numbers.test.ts +48 -1
- package/src/utils/__tests__/objects.test.ts +21 -0
- package/src/utils/arrays.ts +10 -0
- package/src/utils/numbers.ts +28 -17
- package/src/utils/objects.ts +3 -7
- package/src/utils/once.ts +19 -19
- package/dist/assets/CellStatus-BP2mavAq.js +0 -1
- package/dist/assets/JsonOutput-DxHKOpPG.js +0 -11
- package/dist/assets/MarimoErrorOutput-D60XkCny.js +0 -6
- package/dist/assets/ai-model-dropdown-B0z6-zhp.js +0 -5
- package/dist/assets/any-language-editor-BLCAD3yx.js +0 -1
- package/dist/assets/api-BeC_VSGv.js +0 -1
- package/dist/assets/arrays-BqUx5USx.js +0 -1
- package/dist/assets/code-visibility-B-_GvwR5.js +0 -1
- package/dist/assets/coerce-CpE6kUdX.js +0 -1
- package/dist/assets/command-palette-Cf7WX-nr.js +0 -1
- package/dist/assets/config-BOKzA3tF.js +0 -1
- package/dist/assets/config-DX8Y2WbJ.js +0 -1
- package/dist/assets/config-VPSq73n8.js +0 -1
- package/dist/assets/edit-page-BT74CF4G.js +0 -9
- package/dist/assets/feedback-button-B2gKC5cn.js +0 -1
- package/dist/assets/hooks-RnjuFmEx.js +0 -1
- package/dist/assets/index-Bcdzex-3.css +0 -2
- package/dist/assets/index-D-kY3FU1.js +0 -29
- package/dist/assets/linear-0eij_aAG.js +0 -1
- package/dist/assets/links-DkkL9DVa.js +0 -1
- package/dist/assets/numbers-Mk3-CcoU.js +0 -1
- package/dist/assets/pair-CaQyK6En.js +0 -1
- package/dist/assets/panels-Dn59o8ay.js +0 -4
- package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
- package/dist/assets/process-output-D0KGq04R.js +0 -1
- package/dist/assets/react-vega-C3rYGtP6.js +0 -189
- package/dist/assets/session-panel-BxS5hiG2.js +0 -1
- package/dist/assets/state-_Qai_QyX.js +0 -3
- package/dist/assets/tooltip-mT3CGest.js +0 -61
- package/dist/assets/tracing-Di6pUmX8.js +0 -1
- package/dist/assets/useAsyncData-C7aJJz0Y.js +0 -1
- package/dist/assets/useNotebookActions-DIc4xTzY.js +0 -1
- package/dist/assets/vega-component-CnadTgoE.js +0 -1
- package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
- package/src/core/host/__tests__/protocol.test.ts +0 -214
- package/src/core/host/protocol.ts +0 -1943
- package/src/core/websocket/useWebSocket.tsx +0 -88
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { act, render, renderHook, waitFor } from "@testing-library/react";
|
|
3
|
+
import { Activity } from "react";
|
|
4
|
+
import { describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { Deferred } from "@/utils/Deferred";
|
|
6
|
+
import { useAsyncData } from "../useAsyncData";
|
|
7
|
+
|
|
8
|
+
function AsyncDataStatus({ fetch }: { fetch: () => Promise<string> }) {
|
|
9
|
+
const result = useAsyncData(fetch, []);
|
|
10
|
+
return (
|
|
11
|
+
<output
|
|
12
|
+
data-status={result.status}
|
|
13
|
+
data-fetching={result.isFetching}
|
|
14
|
+
data-error={result.error?.message ?? ""}
|
|
15
|
+
>
|
|
16
|
+
{result.data}
|
|
17
|
+
</output>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("useAsyncData", () => {
|
|
22
|
+
it.each(["success", "error"] as const)(
|
|
23
|
+
"restarts fetching when Activity reopens after %s",
|
|
24
|
+
async (initialStatus) => {
|
|
25
|
+
const initial = new Deferred<string>();
|
|
26
|
+
const restarted = new Deferred<string>();
|
|
27
|
+
const fetch = vi
|
|
28
|
+
.fn(() => restarted.promise)
|
|
29
|
+
.mockImplementationOnce(() => initial.promise);
|
|
30
|
+
const view = render(
|
|
31
|
+
<Activity mode="visible">
|
|
32
|
+
<AsyncDataStatus fetch={fetch} />
|
|
33
|
+
</Activity>,
|
|
34
|
+
);
|
|
35
|
+
await act(async () => {
|
|
36
|
+
if (initialStatus === "success") {
|
|
37
|
+
initial.resolve("first");
|
|
38
|
+
} else {
|
|
39
|
+
initial.reject(new Error("offline"));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
expect(view.getByRole("status").dataset.status).toBe(initialStatus);
|
|
43
|
+
|
|
44
|
+
view.rerender(
|
|
45
|
+
<Activity mode="hidden">
|
|
46
|
+
<AsyncDataStatus fetch={fetch} />
|
|
47
|
+
</Activity>,
|
|
48
|
+
);
|
|
49
|
+
view.rerender(
|
|
50
|
+
<Activity mode="visible">
|
|
51
|
+
<AsyncDataStatus fetch={fetch} />
|
|
52
|
+
</Activity>,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(fetch).toHaveBeenCalledTimes(2);
|
|
56
|
+
expect(view.getByRole("status").dataset).toMatchObject({
|
|
57
|
+
status: initialStatus === "success" ? "loading" : "pending",
|
|
58
|
+
error: "",
|
|
59
|
+
fetching: "true",
|
|
60
|
+
});
|
|
61
|
+
expect(view.getByRole("status").textContent).toBe(
|
|
62
|
+
initialStatus === "success" ? "first" : "",
|
|
63
|
+
);
|
|
64
|
+
await act(async () => restarted.resolve("latest"));
|
|
65
|
+
expect(view.getByRole("status").dataset).toMatchObject({
|
|
66
|
+
status: "success",
|
|
67
|
+
fetching: "false",
|
|
68
|
+
});
|
|
69
|
+
expect(view.getByRole("status").textContent).toBe("latest");
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
it("ignores a request canceled when Activity hides", async () => {
|
|
74
|
+
const canceled = new Deferred<string>();
|
|
75
|
+
const restarted = new Deferred<string>();
|
|
76
|
+
const fetch = vi
|
|
77
|
+
.fn(() => restarted.promise)
|
|
78
|
+
.mockImplementationOnce(() => canceled.promise);
|
|
79
|
+
const view = render(
|
|
80
|
+
<Activity mode="visible">
|
|
81
|
+
<AsyncDataStatus fetch={fetch} />
|
|
82
|
+
</Activity>,
|
|
83
|
+
);
|
|
84
|
+
view.rerender(
|
|
85
|
+
<Activity mode="hidden">
|
|
86
|
+
<AsyncDataStatus fetch={fetch} />
|
|
87
|
+
</Activity>,
|
|
88
|
+
);
|
|
89
|
+
view.rerender(
|
|
90
|
+
<Activity mode="visible">
|
|
91
|
+
<AsyncDataStatus fetch={fetch} />
|
|
92
|
+
</Activity>,
|
|
93
|
+
);
|
|
94
|
+
expect(fetch).toHaveBeenCalledTimes(2);
|
|
95
|
+
await act(async () => canceled.resolve("obsolete"));
|
|
96
|
+
expect(view.getByRole("status").dataset.status).toBe("pending");
|
|
97
|
+
expect(view.getByRole("status").textContent).toBe("");
|
|
98
|
+
await act(async () => restarted.resolve("latest"));
|
|
99
|
+
expect(view.getByRole("status").dataset.status).toBe("success");
|
|
100
|
+
expect(view.getByRole("status").textContent).toBe("latest");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("preserves data while refetching and ignores obsolete requests", async () => {
|
|
104
|
+
const requests = [
|
|
105
|
+
new Deferred<string>(),
|
|
106
|
+
new Deferred<string>(),
|
|
107
|
+
new Deferred<string>(),
|
|
108
|
+
];
|
|
109
|
+
const fetch = vi
|
|
110
|
+
.fn(() => requests[0].promise)
|
|
111
|
+
.mockImplementationOnce(() => requests[0].promise)
|
|
112
|
+
.mockImplementationOnce(() => requests[1].promise)
|
|
113
|
+
.mockImplementationOnce(() => requests[2].promise);
|
|
114
|
+
const { result, rerender } = renderHook(
|
|
115
|
+
({ key }) => useAsyncData(fetch, [key]),
|
|
116
|
+
{ initialProps: { key: 0 } },
|
|
117
|
+
);
|
|
118
|
+
expect(result.current.status).toBe("pending");
|
|
119
|
+
await act(async () => requests[0].resolve("first"));
|
|
120
|
+
expect(result.current.data).toBe("first");
|
|
121
|
+
rerender({ key: 1 });
|
|
122
|
+
expect(result.current).toMatchObject({ status: "loading", data: "first" });
|
|
123
|
+
rerender({ key: 2 });
|
|
124
|
+
await act(async () => requests[1].resolve("obsolete"));
|
|
125
|
+
expect(result.current).toMatchObject({ status: "loading", data: "first" });
|
|
126
|
+
await act(async () => requests[2].resolve("latest"));
|
|
127
|
+
expect(result.current).toMatchObject({ status: "success", data: "latest" });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("retries after errors and allows manual updates during a request", async () => {
|
|
131
|
+
const request = new Deferred<string>();
|
|
132
|
+
const fetch = vi
|
|
133
|
+
.fn<() => Promise<string>>()
|
|
134
|
+
.mockRejectedValueOnce(new Error("offline"))
|
|
135
|
+
.mockImplementation(() => request.promise);
|
|
136
|
+
const { result } = renderHook(() => useAsyncData(fetch, []));
|
|
137
|
+
await waitFor(() => expect(result.current.status).toBe("error"));
|
|
138
|
+
act(() => result.current.refetch());
|
|
139
|
+
expect(result.current.status).toBe("pending");
|
|
140
|
+
act(() => result.current.setData("manual"));
|
|
141
|
+
expect(result.current).toMatchObject({ status: "success", data: "manual" });
|
|
142
|
+
await act(async () => request.resolve("response"));
|
|
143
|
+
expect(result.current).toMatchObject({
|
|
144
|
+
status: "success",
|
|
145
|
+
data: "response",
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -322,7 +322,7 @@ export function useAsyncData<T>(
|
|
|
322
322
|
const [nonce, setNonce] = useState(0);
|
|
323
323
|
const [result, setResult] = useState<
|
|
324
324
|
PendingResult<T> | LoadingResult<T> | ErrorResult<T> | SuccessResult<T>
|
|
325
|
-
>(Result.pending());
|
|
325
|
+
>(() => Result.pending());
|
|
326
326
|
|
|
327
327
|
const asProps =
|
|
328
328
|
typeof loaderOrProps === "function"
|
|
@@ -339,12 +339,12 @@ export function useAsyncData<T>(
|
|
|
339
339
|
keepPrevious = true;
|
|
340
340
|
},
|
|
341
341
|
};
|
|
342
|
+
// Activity can restart this effect without changing dependencies, so
|
|
343
|
+
// loading state must follow the request lifecycle.
|
|
342
344
|
setResult((prevResult) => {
|
|
343
|
-
// If we have previous data, show reloading state
|
|
344
345
|
if (prevResult.status === "success" || prevResult.status === "loading") {
|
|
345
346
|
return Result.loading(prevResult.data);
|
|
346
347
|
}
|
|
347
|
-
// Otherwise, show initial loading state
|
|
348
348
|
return Result.pending();
|
|
349
349
|
});
|
|
350
350
|
fetchStable(context)
|
|
@@ -42,11 +42,13 @@ export function useDetectedDataSources(
|
|
|
42
42
|
dialects: [...connections.values()].map((connection) =>
|
|
43
43
|
connection.dialect.toLowerCase(),
|
|
44
44
|
),
|
|
45
|
-
storageProtocols:
|
|
46
|
-
namespace.protocol.toLowerCase(),
|
|
45
|
+
storageProtocols: new Set(
|
|
46
|
+
storageNamespaces.map((namespace) => namespace.protocol.toLowerCase()),
|
|
47
47
|
),
|
|
48
|
-
storageBackendTypes:
|
|
49
|
-
|
|
48
|
+
storageBackendTypes: new Set(
|
|
49
|
+
storageNamespaces.map((namespace) =>
|
|
50
|
+
namespace.backendType.toLowerCase(),
|
|
51
|
+
),
|
|
50
52
|
),
|
|
51
53
|
};
|
|
52
54
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import { useEffect,
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
4
|
import { type Milliseconds, Time } from "@/utils/time";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Returns the elapsed time since mount, in milliseconds.
|
|
8
8
|
*/
|
|
9
9
|
export function useElapsedTime(initialStartTimeMs: Milliseconds) {
|
|
10
|
-
const startTime =
|
|
10
|
+
const [startTime] = useState(initialStartTimeMs);
|
|
11
11
|
const [endTime, setEndTime] = useState(initialStartTimeMs);
|
|
12
12
|
|
|
13
13
|
useEffect(() => {
|
|
@@ -21,5 +21,5 @@ export function useElapsedTime(initialStartTimeMs: Milliseconds) {
|
|
|
21
21
|
return () => clearInterval(interval);
|
|
22
22
|
}, []);
|
|
23
23
|
|
|
24
|
-
return endTime - startTime
|
|
24
|
+
return endTime - startTime;
|
|
25
25
|
}
|
package/src/mount.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
userConfigAtom,
|
|
11
11
|
} from "@/core/config/config";
|
|
12
12
|
import { KnownQueryParams } from "@/core/constants";
|
|
13
|
-
import {
|
|
13
|
+
import { pairConfigAtom, pairConfigSchema } from "@/core/config/pair";
|
|
14
14
|
import { getFilenameFromDOM } from "@/core/dom/htmlUtils";
|
|
15
15
|
import { getMarimoCode } from "@/core/meta/globals";
|
|
16
16
|
import {
|
|
@@ -209,7 +209,7 @@ const mountOptionsSchema = z.object({
|
|
|
209
209
|
* marimo config overrides
|
|
210
210
|
*/
|
|
211
211
|
configOverrides: passthroughObject,
|
|
212
|
-
|
|
212
|
+
pairConfig: pairConfigSchema.optional(),
|
|
213
213
|
/**
|
|
214
214
|
* marimo app config
|
|
215
215
|
*/
|
|
@@ -274,6 +274,8 @@ const mountOptionsSchema = z.object({
|
|
|
274
274
|
.transform((val) => val as api.Notebook["NotebookV1"]),
|
|
275
275
|
]),
|
|
276
276
|
|
|
277
|
+
notebookId: z.string().optional(),
|
|
278
|
+
|
|
277
279
|
/**
|
|
278
280
|
* Runtime configs
|
|
279
281
|
*/
|
|
@@ -284,6 +286,7 @@ const mountOptionsSchema = z.object({
|
|
|
284
286
|
// Lazy by default, but can be overridden by the runtime config
|
|
285
287
|
lazy: z.boolean().default(true),
|
|
286
288
|
authToken: z.string().nullish(),
|
|
289
|
+
notebookId: z.string().optional(),
|
|
287
290
|
}),
|
|
288
291
|
)
|
|
289
292
|
.nullish()
|
|
@@ -347,7 +350,7 @@ function initStore(options: unknown) {
|
|
|
347
350
|
);
|
|
348
351
|
store.set(userConfigAtom, parseUserConfig(parsedOptions.data.config));
|
|
349
352
|
store.set(appConfigAtom, parseAppConfig(parsedOptions.data.appConfig));
|
|
350
|
-
store.set(
|
|
353
|
+
store.set(pairConfigAtom, parsedOptions.data.pairConfig);
|
|
351
354
|
|
|
352
355
|
// Runtime config
|
|
353
356
|
if (parsedOptions.data.runtimeConfig.length > 0) {
|
|
@@ -364,6 +367,7 @@ function initStore(options: unknown) {
|
|
|
364
367
|
} else {
|
|
365
368
|
store.set(runtimeConfigAtom, {
|
|
366
369
|
...DEFAULT_RUNTIME_CONFIG,
|
|
370
|
+
notebookId: parsedOptions.data.notebookId,
|
|
367
371
|
lazy: false,
|
|
368
372
|
serverToken: parsedOptions.data.serverToken,
|
|
369
373
|
});
|
|
@@ -104,7 +104,7 @@ function PluginSlotInternal<T>(
|
|
|
104
104
|
ref: React.Ref<PluginSlotHandle>,
|
|
105
105
|
): JSX.Element {
|
|
106
106
|
const [childNodes, setChildNodes] = useState<ReactNode>(children);
|
|
107
|
-
const [value, setValue] = useState<T>(getInitialValue
|
|
107
|
+
const [value, setValue] = useState<T>(getInitialValue);
|
|
108
108
|
const { theme } = useTheme();
|
|
109
109
|
|
|
110
110
|
const [parsedResult, setParsedResult] = useState(() => {
|
|
@@ -97,13 +97,9 @@ const Dropdown = (props: DropdownProps): JSX.Element => {
|
|
|
97
97
|
id={id}
|
|
98
98
|
disabled={disabled}
|
|
99
99
|
>
|
|
100
|
-
{allowSelectNone ?
|
|
101
|
-
<option value={EMPTY_VALUE} selected={value.length === 0}>
|
|
102
|
-
--
|
|
103
|
-
</option>
|
|
104
|
-
) : null}
|
|
100
|
+
{allowSelectNone ? <option value={EMPTY_VALUE}>--</option> : null}
|
|
105
101
|
{options.map((option) => (
|
|
106
|
-
<option value={option}
|
|
102
|
+
<option value={option} key={option}>
|
|
107
103
|
{option}
|
|
108
104
|
</option>
|
|
109
105
|
))}
|
|
@@ -212,7 +212,7 @@ const Form = ({
|
|
|
212
212
|
// wrapped plugin; this buffered value is in contrast to the actual
|
|
213
213
|
// value of the plugin, which is the value of the wrapped plugin when
|
|
214
214
|
// the submit button was last clicked/activated.
|
|
215
|
-
const [internalValue, setInternalValue] = useState<T>(
|
|
215
|
+
const [internalValue, setInternalValue] = useState<T>(() =>
|
|
216
216
|
UI_ELEMENT_REGISTRY.lookupValue(elementId),
|
|
217
217
|
);
|
|
218
218
|
|
|
@@ -136,6 +136,90 @@ describe("LoadingDataTableComponent", () => {
|
|
|
136
136
|
expect(downloadGeoJSON).toHaveBeenCalledTimes(1);
|
|
137
137
|
});
|
|
138
138
|
|
|
139
|
+
it("refreshes export metadata when a dataframe transform changes the data", async () => {
|
|
140
|
+
const host = document.createElement("div");
|
|
141
|
+
const metadataFor = (name: string) => ({
|
|
142
|
+
geometry_columns: [{ name, encoding: "objects", crs: "EPSG:4326" }],
|
|
143
|
+
primary_geometry_column: name,
|
|
144
|
+
default_geometry_column: name,
|
|
145
|
+
formats: {
|
|
146
|
+
parquet: { available: true, reason: null, missing_packages: [] },
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
const getExportMetadata = vi
|
|
150
|
+
.fn()
|
|
151
|
+
.mockResolvedValueOnce(metadataFor("before"))
|
|
152
|
+
.mockResolvedValueOnce(metadataFor("after"));
|
|
153
|
+
const view = (data: string) => (
|
|
154
|
+
<Provider store={store}>
|
|
155
|
+
<TooltipProvider>
|
|
156
|
+
<LoadingDataTableComponent
|
|
157
|
+
label={null}
|
|
158
|
+
totalRows={1}
|
|
159
|
+
pagination={false}
|
|
160
|
+
pageSize={10}
|
|
161
|
+
selection={null}
|
|
162
|
+
showDownload={true}
|
|
163
|
+
showFilters={false}
|
|
164
|
+
showColumnSummaries={false}
|
|
165
|
+
showDataTypes={false}
|
|
166
|
+
showPageSizeSelector={false}
|
|
167
|
+
showColumnExplorer={false}
|
|
168
|
+
showRowExplorer={false}
|
|
169
|
+
showChartBuilder={false}
|
|
170
|
+
rowHeaders={[]}
|
|
171
|
+
fieldTypes={undefined}
|
|
172
|
+
totalColumns={1}
|
|
173
|
+
maxColumns="all"
|
|
174
|
+
hasStableRowId={false}
|
|
175
|
+
lazy={false}
|
|
176
|
+
host={host}
|
|
177
|
+
showSearch={false}
|
|
178
|
+
value={[]}
|
|
179
|
+
setValue={vi.fn()}
|
|
180
|
+
data={data}
|
|
181
|
+
search={vi.fn().mockResolvedValue({
|
|
182
|
+
data,
|
|
183
|
+
total_rows: 1,
|
|
184
|
+
cell_styles: null,
|
|
185
|
+
cell_hover_texts: null,
|
|
186
|
+
})}
|
|
187
|
+
download_as={vi.fn()}
|
|
188
|
+
download_geojson={vi.fn()}
|
|
189
|
+
get_export_metadata={getExportMetadata}
|
|
190
|
+
get_column_summaries={vi.fn()}
|
|
191
|
+
get_data_url={vi.fn() as GetDataUrl}
|
|
192
|
+
get_row_ids={vi.fn() as GetRowIds}
|
|
193
|
+
get_size_bytes={vi.fn().mockResolvedValue({ size_bytes: null })}
|
|
194
|
+
/>
|
|
195
|
+
</TooltipProvider>
|
|
196
|
+
</Provider>
|
|
197
|
+
);
|
|
198
|
+
const { rerender } = render(
|
|
199
|
+
view(JSON.stringify([{ before: "POINT (1 2)" }])),
|
|
200
|
+
);
|
|
201
|
+
await waitFor(() =>
|
|
202
|
+
expect(
|
|
203
|
+
screen.getByRole("columnheader", { name: "before" }),
|
|
204
|
+
).toBeInTheDocument(),
|
|
205
|
+
);
|
|
206
|
+
fireEvent.click(screen.getByTestId("export-button"));
|
|
207
|
+
await waitFor(() =>
|
|
208
|
+
expect(screen.getByTestId("export-summary-parquet")).toHaveTextContent(
|
|
209
|
+
"before",
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
rerender(view(JSON.stringify([{ after: "POINT (3 4)" }])));
|
|
214
|
+
|
|
215
|
+
await waitFor(() =>
|
|
216
|
+
expect(screen.getByTestId("export-summary-parquet")).toHaveTextContent(
|
|
217
|
+
"after",
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
expect(getExportMetadata).toHaveBeenCalledTimes(2);
|
|
221
|
+
});
|
|
222
|
+
|
|
139
223
|
it("does not log duplicate keys when a pivot becomes empty", async () => {
|
|
140
224
|
const host = document.createElement("div");
|
|
141
225
|
const consoleError = vi
|
|
@@ -53,14 +53,14 @@ const AnyLanguageCodeMirror: React.FC<
|
|
|
53
53
|
extensions?: readonly Extension[];
|
|
54
54
|
}
|
|
55
55
|
> = ({
|
|
56
|
-
language,
|
|
56
|
+
language: requestedLanguage,
|
|
57
57
|
hideUnsupportedLanguageErrors,
|
|
58
58
|
showCopyButton,
|
|
59
59
|
extensions = EMPTY_EXTENSIONS,
|
|
60
60
|
...props
|
|
61
61
|
}) => {
|
|
62
62
|
// Maybe normalize the language to the extension
|
|
63
|
-
language = LANGUAGE_MAP[
|
|
63
|
+
const language = LANGUAGE_MAP[requestedLanguage || ""] || requestedLanguage;
|
|
64
64
|
|
|
65
65
|
const isSupported = isSupportedLanguage(language);
|
|
66
66
|
if (!isSupported) {
|
|
@@ -73,12 +73,16 @@ export const GlideDataEditor = ({
|
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
const [columnWidths, setColumnWidths] = useState<Record<string, number>>({});
|
|
76
|
+
const editableColumnSet = useMemo(
|
|
77
|
+
() => (editableColumns === "all" ? null : new Set(editableColumns)),
|
|
78
|
+
[editableColumns],
|
|
79
|
+
);
|
|
76
80
|
|
|
77
81
|
const columns: ModifiedGridColumn[] = useMemo(() => {
|
|
78
82
|
const columns: ModifiedGridColumn[] = [];
|
|
79
83
|
for (const [columnName, fieldType] of columnFields) {
|
|
80
84
|
const editable =
|
|
81
|
-
|
|
85
|
+
editableColumnSet === null || editableColumnSet.has(columnName);
|
|
82
86
|
|
|
83
87
|
columns.push({
|
|
84
88
|
id: columnName,
|
|
@@ -100,7 +104,7 @@ export const GlideDataEditor = ({
|
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
return columns;
|
|
103
|
-
}, [columnFields, columnWidths,
|
|
107
|
+
}, [columnFields, columnWidths, editableColumnSet, theme]);
|
|
104
108
|
|
|
105
109
|
const getCellContent = useCallback(
|
|
106
110
|
(cell: Item): GridCell => {
|
|
@@ -110,8 +114,7 @@ export const GlideDataEditor = ({
|
|
|
110
114
|
const dataItem = dataRow[columns[col].title];
|
|
111
115
|
const columnKind = columns[col].kind;
|
|
112
116
|
const editable =
|
|
113
|
-
|
|
114
|
-
editableColumns.includes(columns[col].title);
|
|
117
|
+
editableColumnSet === null || editableColumnSet.has(columns[col].title);
|
|
115
118
|
|
|
116
119
|
if (columnKind === GridCellKind.Boolean) {
|
|
117
120
|
const value = Boolean(dataItem);
|
|
@@ -141,7 +144,7 @@ export const GlideDataEditor = ({
|
|
|
141
144
|
data: String(dataItem),
|
|
142
145
|
};
|
|
143
146
|
},
|
|
144
|
-
[columns, data,
|
|
147
|
+
[columns, data, editableColumnSet],
|
|
145
148
|
);
|
|
146
149
|
|
|
147
150
|
const onCellEdited = useCallback(
|
|
@@ -147,6 +147,8 @@ export function pasteCells(options: {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
const edits: Edits["edits"] = [];
|
|
150
|
+
const editableColumnSet =
|
|
151
|
+
editableColumns === "all" ? null : new Set(editableColumns);
|
|
150
152
|
|
|
151
153
|
for (const [rowIndex, dataRow] of parsedData.entries()) {
|
|
152
154
|
if (!dataRow) {
|
|
@@ -175,7 +177,7 @@ export function pasteCells(options: {
|
|
|
175
177
|
const column = columns[targetColIdx];
|
|
176
178
|
const columnType = column.dataType;
|
|
177
179
|
const editable =
|
|
178
|
-
|
|
180
|
+
editableColumnSet === null || editableColumnSet.has(column.title);
|
|
179
181
|
|
|
180
182
|
if (!editable) {
|
|
181
183
|
continue;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import type { TopLevelSpec } from "vega-lite";
|
|
4
4
|
import type { NonNormalizedSpec } from "vega-lite/types_unstable/spec/index.js";
|
|
5
5
|
import type { TopLevelParameter } from "vega-lite/types_unstable/spec/toplevel.js";
|
|
6
|
+
import { Arrays } from "@/utils/arrays";
|
|
6
7
|
|
|
7
8
|
import { findEncodedFields, makeEncodingInteractive } from "./encodings";
|
|
8
9
|
import { Marks } from "./marks";
|
|
@@ -357,9 +358,7 @@ export function makeSelectable<T extends VegaLiteSpec>(
|
|
|
357
358
|
|
|
358
359
|
// If fieldSelection is an array, filter the fields
|
|
359
360
|
if (Array.isArray(fieldSelection)) {
|
|
360
|
-
legendFields =
|
|
361
|
-
fieldSelection.includes(field),
|
|
362
|
-
);
|
|
361
|
+
legendFields = Arrays.intersection(legendFields, fieldSelection);
|
|
363
362
|
}
|
|
364
363
|
}
|
|
365
364
|
|
|
@@ -420,9 +419,7 @@ function makeLegendSelectable(
|
|
|
420
419
|
let legendFields = findEncodedFields(spec);
|
|
421
420
|
// If fieldSelection is an array, we filter the fields
|
|
422
421
|
if (Array.isArray(fieldSelection)) {
|
|
423
|
-
legendFields =
|
|
424
|
-
fieldSelection.includes(field),
|
|
425
|
-
);
|
|
422
|
+
legendFields = Arrays.intersection(legendFields, fieldSelection);
|
|
426
423
|
}
|
|
427
424
|
|
|
428
425
|
const legendParams = legendFields.map((field) => Params.legend(field));
|
|
@@ -16,6 +16,41 @@ import {
|
|
|
16
16
|
} from "../arrays";
|
|
17
17
|
|
|
18
18
|
describe("arrays", () => {
|
|
19
|
+
describe.each([
|
|
20
|
+
["intersection", Arrays.intersection, [3, 3, 2], [Number.NaN]],
|
|
21
|
+
["difference", Arrays.difference, [1], [1]],
|
|
22
|
+
] as const)("%s", (_name, operation, expected, expectedWithNaN) => {
|
|
23
|
+
it("preserves order and duplicates without mutating readonly inputs", () => {
|
|
24
|
+
const values = Object.freeze([3, 1, 3, 2]);
|
|
25
|
+
const other = Object.freeze([2, 3, 3]);
|
|
26
|
+
expect(operation(values, other)).toEqual(expected);
|
|
27
|
+
expect(values).toEqual([3, 1, 3, 2]);
|
|
28
|
+
expect(other).toEqual([2, 3, 3]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("uses the same equality as array membership, including NaN", () => {
|
|
32
|
+
expect(operation([Number.NaN, 1], [Number.NaN])).toEqual(expectedWithNaN);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("handles an empty source", () => {
|
|
36
|
+
expect(operation([], [1])).toEqual([]);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("handles empty membership lists", () => {
|
|
41
|
+
expect(Arrays.intersection([1, 1, 2], [])).toEqual([]);
|
|
42
|
+
expect(Arrays.difference([1, 1, 2], [])).toEqual([1, 1, 2]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("compares objects by identity", () => {
|
|
46
|
+
const shared = { id: 1 };
|
|
47
|
+
const distinct = { id: 1 };
|
|
48
|
+
expect(Arrays.intersection([shared, distinct], [shared])).toEqual([shared]);
|
|
49
|
+
expect(Arrays.difference([shared, distinct], [shared])).toEqual([distinct]);
|
|
50
|
+
expect(Arrays.intersection([shared, distinct], [shared])[0]).toBe(shared);
|
|
51
|
+
expect(Arrays.difference([shared, distinct], [shared])[0]).toBe(distinct);
|
|
52
|
+
});
|
|
53
|
+
|
|
19
54
|
describe("arrayDelete", () => {
|
|
20
55
|
it("should delete an element at the specified index", () => {
|
|
21
56
|
expect(arrayDelete([1, 2, 3], 1)).toEqual([1, 3]);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
3
|
import {
|
|
4
4
|
countFractionDigits,
|
|
5
5
|
maxFractionDigitsForSteps,
|
|
@@ -20,6 +20,53 @@ describe("prettyNumber", () => {
|
|
|
20
20
|
|
|
21
21
|
const options = { locale };
|
|
22
22
|
|
|
23
|
+
describe("formatter reuse", () => {
|
|
24
|
+
it.each([
|
|
25
|
+
{
|
|
26
|
+
name: "scientific",
|
|
27
|
+
value: 0.000_123_4,
|
|
28
|
+
format: (value: number, locale: string) =>
|
|
29
|
+
prettyScientificNumber(value, { locale }),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "rounded",
|
|
33
|
+
value: 1234.567,
|
|
34
|
+
format: (value: number, locale: string) =>
|
|
35
|
+
prettyScientificNumber(value, { locale, shouldRound: true }),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "engineering",
|
|
39
|
+
value: 12_345,
|
|
40
|
+
format: prettyEngineeringNumber,
|
|
41
|
+
},
|
|
42
|
+
])(
|
|
43
|
+
"reuses the $name formatter until the locale changes",
|
|
44
|
+
({ format, value }) => {
|
|
45
|
+
const NumberFormat = Intl.NumberFormat;
|
|
46
|
+
const constructor = vi
|
|
47
|
+
.spyOn(Intl, "NumberFormat")
|
|
48
|
+
.mockImplementation(function (locales, options) {
|
|
49
|
+
return new NumberFormat(locales, options);
|
|
50
|
+
});
|
|
51
|
+
try {
|
|
52
|
+
const first = format(value, "fr-FR");
|
|
53
|
+
expect(format(value, "fr-FR")).toBe(first);
|
|
54
|
+
format(value * 2, "fr-FR");
|
|
55
|
+
expect(constructor).toHaveBeenCalledTimes(1);
|
|
56
|
+
|
|
57
|
+
format(value, "de-DE");
|
|
58
|
+
expect(constructor).toHaveBeenCalledTimes(2);
|
|
59
|
+
expect(constructor).toHaveBeenLastCalledWith(
|
|
60
|
+
"de-DE",
|
|
61
|
+
expect.any(Object),
|
|
62
|
+
);
|
|
63
|
+
} finally {
|
|
64
|
+
constructor.mockRestore();
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
23
70
|
describe("prettyScientificNumber", () => {
|
|
24
71
|
it("should handle special cases", () => {
|
|
25
72
|
expect(prettyScientificNumber(0, options)).toBe("0");
|
|
@@ -204,6 +204,27 @@ describe("Objects", () => {
|
|
|
204
204
|
});
|
|
205
205
|
|
|
206
206
|
describe("filter", () => {
|
|
207
|
+
it.each([42, { base_url: "https://example.com" }])(
|
|
208
|
+
"preserves an own __proto__ property with value %j",
|
|
209
|
+
(value) => {
|
|
210
|
+
const obj = { ["__proto__"]: value, removed: value };
|
|
211
|
+
const result = Objects.filter(obj, (_, key) => key !== "removed");
|
|
212
|
+
|
|
213
|
+
expect(Object.entries(result)).toEqual([["__proto__", value]]);
|
|
214
|
+
expect(Object.getPrototypeOf(result)).toBe(Object.prototype);
|
|
215
|
+
expect(Object.getOwnPropertyDescriptor(result, "__proto__")).toEqual({
|
|
216
|
+
value,
|
|
217
|
+
writable: true,
|
|
218
|
+
enumerable: true,
|
|
219
|
+
configurable: true,
|
|
220
|
+
});
|
|
221
|
+
expect(Object.keys(obj)).toEqual(["__proto__", "removed"]);
|
|
222
|
+
expect(Objects.filter(obj, (_, key) => key !== "__proto__")).toEqual({
|
|
223
|
+
removed: value,
|
|
224
|
+
});
|
|
225
|
+
},
|
|
226
|
+
);
|
|
227
|
+
|
|
207
228
|
it("should filter object entries by predicate", () => {
|
|
208
229
|
const obj = { a: 1, b: 2, c: 3, d: 4 };
|
|
209
230
|
const result = Objects.filter(obj, (v) => v % 2 === 0);
|
package/src/utils/arrays.ts
CHANGED
|
@@ -55,6 +55,16 @@ export function arrayShallowEquals<T>(a: T[], b: T[]): boolean {
|
|
|
55
55
|
export const Arrays = {
|
|
56
56
|
// oxlint-disable-next-line typescript/no-explicit-any
|
|
57
57
|
EMPTY: [] as any,
|
|
58
|
+
/** Keep values present in both arrays, preserving the first array's order and duplicates. */
|
|
59
|
+
intersection<T>(values: readonly T[], included: readonly T[]): T[] {
|
|
60
|
+
const includedSet = new Set(included);
|
|
61
|
+
return values.filter((value) => includedSet.has(value));
|
|
62
|
+
},
|
|
63
|
+
/** Remove values present in the second array, preserving order and duplicates. */
|
|
64
|
+
difference<T>(values: readonly T[], excluded: readonly T[]): T[] {
|
|
65
|
+
const excludedSet = new Set(excluded);
|
|
66
|
+
return values.filter((value) => !excludedSet.has(value));
|
|
67
|
+
},
|
|
58
68
|
zip: <T, U>(a: T[], b: U[]): [T, U][] => {
|
|
59
69
|
invariant(a.length === b.length, "Arrays must be the same length");
|
|
60
70
|
return a.map((item, i) => [item, b[i]]);
|