@marimo-team/islands 0.25.3-dev141 → 0.25.3-dev143
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/{ConnectedDataExplorerComponent-CNSxdKyJ.js → ConnectedDataExplorerComponent-BAiSVaHj.js} +17 -15
- package/dist/{ErrorBoundary-Cg59O3xL.js → ErrorBoundary-BRBKqg2U.js} +16 -16
- package/dist/{abnfDiagram-VCTEODGH-DQDjNXt3.js → abnfDiagram-VCTEODGH-BBHN92-F.js} +2 -2
- package/dist/any-language-editor-3E24SnCV.js +75 -0
- package/dist/{architectureDiagram-5GKGNRK7-Nmls7Wxn.js → architectureDiagram-5GKGNRK7-C0Rd64qh.js} +1 -1
- package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
- package/dist/assets/__vite-browser-external-BgQNn4dS.js +1 -0
- package/dist/assets/worker-FKe3RHT4.js +113 -0
- package/dist/{button-CvjkwdSk.js → button-BQRdqQi1.js} +199 -192
- package/dist/{capabilities-CQ5IjhzL.js → capabilities-CI4xUPsp.js} +2 -2
- package/dist/{chat-ui-BDpymeoe.js → chat-ui-CvB8Map3.js} +1195 -1195
- package/dist/{check-EDwSwP89.js → check-SpOMoCLP.js} +1 -1
- package/dist/{chunk-IMKFNOWR-CoIFEo-1.js → chunk-IMKFNOWR-hJXey5p2.js} +1 -1
- package/dist/{chunk-SHT3W25Y-lHiGx3ZC.js → chunk-SHT3W25Y-JMzTmmVW.js} +1 -1
- package/dist/{chunk-SVP7TREG-BvsHIrY_.js → chunk-SVP7TREG-BIyoHTXi.js} +1 -1
- package/dist/{chunk-TICWLB2K-BC8YIm_n.js → chunk-TICWLB2K-CCzH2Uwa.js} +1 -1
- package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
- package/dist/{classDiagram-ZZMXUADV-Bt98lkkv.js → classDiagram-ZZMXUADV-DB1kOPS6.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-D9bfbcC3.js → classDiagram-v2-VYDZK3BY-CPaFL0OB.js} +2 -2
- package/dist/{code-visibility-Da3iitn_.js → code-visibility-CAgcNzoX.js} +17 -17
- package/dist/{common-C6FHcu5p.js → common-BovIV1xq.js} +6341 -5870
- package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-C1trdvVt.js} +3 -3
- package/dist/{cynefinDiagram-5FMLGOSQ-CgWra7wt.js → cynefinDiagram-5FMLGOSQ-CF93uliH.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-CFZ8-35c.js → dagre-GXQ25YYZ-BBB-_p9D.js} +1 -1
- package/dist/{data-grid-overlay-editor-mJt3Pg4S.js → data-grid-overlay-editor-CBsr8Yo0.js} +5 -5
- package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-0PJ-9N5n.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-lCNQrW7d.js → diagram-S7CK7UJ4-BynCnbyt.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-CQPQ6pP_.js → diagram-UQ7AKVKN-svOxikMa.js} +1 -1
- package/dist/{diagram-VX7I27RA-Cf3b0llz.js → diagram-VX7I27RA-BVQSnPx0.js} +1 -1
- package/dist/{diagram-Z3DM3KII-Bt4QLrpn.js → diagram-Z3DM3KII-nR1J0BH5.js} +1 -1
- package/dist/{dialog-CEBFZNyc.js → dialog-DgeqyrEZ.js} +3 -3
- package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
- package/dist/{dropdown-menu-CP_qCYHC.js → dropdown-menu-GWcGCcxE.js} +1277 -1229
- package/dist/{ebnfDiagram-PWID7BFC-D3NAyPFY.js → ebnfDiagram-PWID7BFC-C9tq9-Qo.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-Tlu09_Nm.js → erDiagram-RLTQ6QDP-BiaeB7Fi.js} +1 -1
- package/dist/{error-banner-Da600cwz.js → error-banner-D40pmfQQ.js} +86 -81
- package/dist/{cells-C8iCYvLU.js → error-entries-CmFDQ9CA.js} +1718 -1621
- package/dist/{feedback-button-DaPwHIdM.js → feedback-button-B_9o6DdV.js} +122 -123
- package/dist/{flowDiagram-HODETNUW-CAYbpJmV.js → flowDiagram-HODETNUW-f-sLm87k.js} +2 -2
- package/dist/{formats-BGh2thRc.js → formats-CCxaLPNs.js} +3 -3
- package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
- package/dist/{glide-data-editor-Ctz2mH6k.js → glide-data-editor-i7BEAfhs.js} +370 -368
- package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Dz_F4pAY.js} +16 -16
- package/dist/{globals-EpFoRj_E.js → globals-BDzA_Re9.js} +3 -3
- package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
- package/dist/{html-to-image-CZY1pIZH.js → html-to-image-BrC8OJcj.js} +634 -597
- package/dist/{infoDiagram-27XIBGKW-XrDxhr0x.js → infoDiagram-27XIBGKW-BHrWywBe.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-CD1muzov.js → ishikawaDiagram-5VMMS53U-Da4s0Reg.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-Dg9W142_.js → kanban-definition-UXKFOSKX-DDf1GVLj.js} +1 -1
- package/dist/{label-BDKF3Afc.js → label-D9_-zSg-.js} +3 -3
- package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
- package/dist/{loader-BQOfjTIJ.js → loader-DOVB0Syl.js} +12 -12
- package/dist/main.js +7548 -7226
- package/dist/{mermaid-CiAXjLpA.js → mermaid-ClwTt7OD.js} +38 -38
- package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
- package/dist/{mindmap-definition-YA3MSWOX-DgCidgYJ.js → mindmap-definition-YA3MSWOX-D60oMZtT.js} +1 -1
- package/dist/{number-overlay-editor-CtxrummL.js → number-overlay-editor-D9WjQlOL.js} +3 -3
- package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
- package/dist/{once-BtE-wKRL.js → once-eG8Vmvjd.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-coOYYKB6.js → pegDiagram-XKGWAZYB-B9wFpMga.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-BnXTc3bj.js → pieDiagram-E7YTZNPT-BZwvMuBT.js} +1 -1
- package/dist/precisionRound-DhAzeRcm.js +49 -0
- package/dist/{process-output-e4jj9Ijt.js → process-output-Bw-vzyoy.js} +4 -4
- package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
- package/dist/{railroadDiagram-O6MQD6OU-B28-u83n.js → railroadDiagram-O6MQD6OU-DqhZGsN2.js} +2 -2
- package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
- package/dist/{requirementDiagram-BXWQKSXE-CdYCTMu0.js → requirementDiagram-BXWQKSXE-CL5dYARV.js} +1 -1
- package/dist/{reveal-component-DrIXvR_7.js → reveal-component-BxP2jlPx.js} +14 -14
- package/dist/{select-BButFi-d.js → select-CkF_GBxO.js} +537 -458
- package/dist/{sequenceDiagram-WJ2MYXX4-CM3aRAe_.js → sequenceDiagram-WJ2MYXX4-BSUCRd2x.js} +1 -1
- package/dist/{spec-BwarBpUJ.js → spec-DiSh_qJC.js} +41 -11
- package/dist/{stateDiagram-D77RDMKH-DOZUMVj8.js → stateDiagram-D77RDMKH-B-1Hvyb0.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-CISYxma6.js → stateDiagram-v2-MP3YSRHH--HtiR4yB.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-BkSppxp8.js → swimlanes-42K2YHIH-Bdfhrd-2.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-c8G5pGxC.js → swimlanesDiagram-VR7AAH4N-BJIfxSz0.js} +2 -2
- package/dist/{swiper-component-DKntyn8p.js → swiper-component-BkCm-kLg.js} +2 -2
- package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
- package/dist/{timeline-definition-24CTP7MA-CaMQayqK.js → timeline-definition-24CTP7MA-Cs4EfFD3.js} +1 -1
- package/dist/{toDate-BNJf01u1.js → toDate-DGvRHwTk.js} +57 -54
- package/dist/{toast-B9UMEi8u.js → toast-Btbve3Lm.js} +231 -213
- package/dist/{tooltip-BO1REbqs.js → tooltip-CJ4Rqasp.js} +327 -303
- package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-CmGwRNId.js} +1482 -1433
- package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-BmVgnMP5.js} +15 -14
- package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-eCL_SdeG.js} +2 -2
- package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-Ch_AeGu6.js} +1 -1
- package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-CSdWn6NP.js} +1 -1
- package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-B5BX921H.js} +3 -3
- package/dist/{useTheme-CRnjkku0.js → useTheme-Bq9pi_vz.js} +6 -6
- package/dist/{vega-component-DG29TAB7.js → vega-component-BgyZ-COt.js} +147 -147
- package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
- package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
- package/dist/{vennDiagram-4TSXK5OY-CF8pfaK3.js → vennDiagram-4TSXK5OY-DRu0vRjO.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-dGPu39d-.js → wardleyDiagram-VM6X3IG4-DzbDxwDY.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-CP6-9q56.js → xychartDiagram-S5SC5T6Z-CLWj6ZFQ.js} +2 -2
- package/package.json +15 -12
- package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
- package/src/__tests__/host-imports.test.ts +101 -0
- package/src/__tests__/module-entry.test.ts +32 -0
- package/src/__tests__/mount.test.ts +8 -8
- package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
- package/src/components/ai/ai-model-dropdown.tsx +11 -6
- package/src/components/app-config/ai-config.tsx +7 -2
- package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
- package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
- package/src/components/chat/acp/__tests__/state.test.ts +2 -2
- package/src/components/chat/acp/blocks.tsx +3 -2
- package/src/components/chat/acp/session-tabs.tsx +8 -3
- package/src/components/chat/chat-components.tsx +2 -0
- package/src/components/chat/chat-panel.tsx +9 -8
- package/src/components/chat/chat-utils.ts +4 -8
- package/src/components/data-table/TableTopBar.tsx +2 -0
- package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
- package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
- package/src/components/data-table/__tests__/columns.test.tsx +105 -0
- package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
- package/src/components/data-table/__tests__/context-menu.test.ts +176 -0
- package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
- package/src/components/data-table/__tests__/date-filter-inputs.test.tsx +24 -1
- package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
- package/src/components/data-table/__tests__/export-actions.test.tsx +434 -6
- package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
- package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
- package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
- package/src/components/data-table/cell-selection/feature.ts +2 -2
- package/src/components/data-table/cell-selection/types.ts +2 -2
- package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
- package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
- package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
- package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
- package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
- package/src/components/data-table/charts/charts.tsx +40 -30
- package/src/components/data-table/charts/storage.ts +16 -5
- package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
- package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
- package/src/components/data-table/column-summary/column-summary.tsx +2 -4
- package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
- package/src/components/data-table/columns.tsx +45 -46
- package/src/components/data-table/context-menu.tsx +161 -46
- package/src/components/data-table/data-table.tsx +13 -1
- package/src/components/data-table/date-filter-inputs.tsx +15 -42
- package/src/components/data-table/date-popover.tsx +6 -4
- package/src/components/data-table/export-actions.tsx +267 -163
- package/src/components/data-table/range-focus/atoms.ts +4 -6
- package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
- package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
- package/src/components/data-table/renderers.tsx +16 -7
- package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
- package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
- package/src/components/data-table/schemas.ts +14 -36
- package/src/components/data-table/search-highlight.tsx +79 -0
- package/src/components/data-table/types.ts +1 -0
- package/src/components/data-table/url-detector.tsx +49 -9
- package/src/components/data-table/utils.ts +18 -0
- package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
- package/src/components/datasources/__tests__/column-preview.test.tsx +98 -5
- package/src/components/datasources/__tests__/datasources-interactions.test.tsx +181 -0
- package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
- package/src/components/datasources/__tests__/utils.test.ts +57 -6
- package/src/components/datasources/column-preview.tsx +13 -4
- package/src/components/datasources/datasources.tsx +51 -39
- package/src/components/datasources/utils.ts +32 -23
- package/src/components/debugger/debugger-code.tsx +5 -0
- package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
- package/src/components/dependency-graph/elements.ts +24 -21
- package/src/components/dependency-graph/panels.tsx +2 -1
- package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
- package/src/components/editor/Output.tsx +9 -2
- package/src/components/editor/SortableCell.tsx +1 -3
- package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
- package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
- 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 +210 -34
- package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
- package/src/components/editor/actions/pair-with-agent-modal.tsx +100 -37
- package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
- package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
- package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
- package/src/components/editor/cell/CreateCellButton.tsx +8 -10
- package/src/components/editor/cell/DeleteButton.tsx +1 -0
- package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
- package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
- package/src/components/editor/cell/code/cell-editor.tsx +1 -1
- package/src/components/editor/cell/code/language-toggle.tsx +8 -7
- package/src/components/editor/cell/collapse.tsx +5 -3
- package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
- package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
- package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
- package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
- package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
- package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
- package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
- package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
- package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
- package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -1
- package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +366 -0
- package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
- 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-items/runtime-settings.tsx +1 -7
- package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
- package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
- package/src/components/editor/chrome/wrapper/storage.ts +1 -1
- 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/__tests__/keyboard-shortcuts.test.tsx +61 -0
- package/src/components/editor/controls/command-palette.tsx +22 -10
- package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
- 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/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
- package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
- package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
- package/src/components/editor/file-tree/file-explorer.tsx +45 -8
- package/src/components/editor/file-tree/file-name-input.tsx +1 -0
- package/src/components/editor/file-tree/file-viewer.tsx +4 -2
- package/src/components/editor/file-tree/use-file-search.ts +35 -21
- package/src/components/editor/header/status.tsx +28 -5
- package/src/components/editor/links/__tests__/cell-link.test.tsx +146 -0
- package/src/components/editor/links/cell-link.tsx +14 -5
- package/src/components/editor/navigation/focus-utils.ts +4 -4
- package/src/components/editor/notebook-banner.tsx +1 -0
- package/src/components/editor/notebook-cell.tsx +11 -2
- package/src/components/editor/output/MarimoErrorOutput.tsx +21 -5
- package/src/components/editor/output/MarimoTracebackOutput.tsx +54 -37
- package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
- package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
- package/src/components/editor/output/ansi-reduce.ts +36 -17
- package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
- package/src/components/editor/package-alert.tsx +14 -7
- package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
- package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
- package/src/components/home/components.tsx +17 -31
- package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
- package/src/components/icons/large-spinner.tsx +3 -14
- package/src/components/icons/spinner.tsx +1 -1
- package/src/components/mcp/mcp-status-indicator.tsx +1 -1
- package/src/components/pages/__tests__/home-page.test.tsx +116 -0
- package/src/components/pages/home-page.tsx +164 -91
- package/src/components/scratchpad/scratchpad.test.tsx +95 -0
- package/src/components/scratchpad/scratchpad.tsx +16 -3
- package/src/components/slides/reveal-component.tsx +4 -2
- package/src/components/slides/slide-cell-view.tsx +1 -1
- package/src/components/slides/slide-form.tsx +1 -0
- package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
- package/src/components/tracing/tracing.tsx +45 -41
- package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
- package/src/components/ui/__tests__/range-slider.test.tsx +165 -0
- package/src/components/ui/alert-dialog.tsx +1 -1
- package/src/components/ui/draggable-popover.tsx +24 -2
- package/src/components/ui/range-slider.tsx +22 -9
- package/src/core/ai/context/providers/datasource.ts +6 -2
- package/src/core/ai/tools/base.ts +16 -7
- package/src/core/ai/tools/edit-notebook-tool.ts +47 -45
- package/src/core/ai/tools/registry.ts +13 -8
- package/src/core/ai/tools/sample-tool.ts +6 -7
- 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__/__snapshots__/cells.test.ts.snap +4 -0
- package/src/core/cells/__tests__/cell.test.ts +39 -0
- package/src/core/cells/__tests__/cells.test.ts +55 -1
- package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -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 +167 -37
- package/src/core/cells/cell.ts +14 -3
- package/src/core/cells/cells.ts +3 -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 +29 -2
- package/src/core/cells/types.ts +10 -1
- package/src/core/codemirror/__tests__/setup.test.ts +29 -29
- package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -0
- package/src/core/codemirror/cells/extensions.ts +7 -3
- package/src/core/codemirror/keymaps/vim.ts +2 -6
- package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
- package/src/core/codemirror/language/extension.ts +8 -8
- package/src/core/codemirror/language/languages/markdown.ts +8 -6
- package/src/core/codemirror/language/languages/python.ts +16 -10
- package/src/core/codemirror/language/languages/sql/sql.ts +9 -7
- package/src/core/codemirror/language/panel/panel.tsx +1 -0
- package/src/core/codemirror/language/types.ts +7 -7
- package/src/core/codemirror/rtc/extension.ts +24 -12
- package/src/core/codemirror/rtc/loro/awareness.ts +34 -19
- package/src/core/codemirror/rtc/loro/sync.ts +1 -0
- package/src/core/codemirror/utils.ts +1 -0
- 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/document/__tests__/codemirror.test.ts +485 -0
- package/src/core/document/__tests__/draft.test.ts +322 -0
- package/src/core/document/__tests__/fixtures.ts +92 -0
- package/src/core/document/__tests__/hooks.test.tsx +386 -0
- package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
- package/src/core/document/__tests__/model.test.ts +191 -0
- package/src/core/document/__tests__/protocol-fixtures.ts +38 -0
- package/src/core/document/__tests__/recovery-scenarios.test.ts +230 -0
- package/src/core/document/__tests__/scenario-fixture.ts +43 -0
- package/src/core/document/__tests__/scenarios.test.ts +205 -0
- package/src/core/document/__tests__/state.test.ts +438 -0
- package/src/core/document/__tests__/submission.test.ts +203 -0
- package/src/core/document/__tests__/test-host.test.ts +92 -0
- package/src/core/document/__tests__/test-host.ts +247 -0
- package/src/core/document/__tests__/test-notebook.ts +149 -0
- package/src/core/document/__tests__/user.ts +61 -0
- package/src/core/document/codemirror.ts +311 -0
- package/src/core/document/draft.ts +472 -0
- package/src/core/document/hooks.tsx +118 -0
- package/src/core/document/index.ts +15 -0
- package/src/core/document/model.ts +55 -0
- package/src/core/document/select.ts +41 -0
- package/src/core/document/state.ts +374 -0
- package/src/core/document/sync.ts +369 -0
- package/src/core/edit-app.tsx +2 -2
- package/src/core/host/AGENTS.md +12 -0
- 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__/fixtures.ts +79 -0
- package/src/core/host/__tests__/replica.test.ts +550 -0
- package/src/core/host/__tests__/static.test.ts +175 -0
- package/src/core/host/__tests__/wasm.test.ts +166 -0
- package/src/core/host/backend.ts +19 -0
- package/src/core/host/connection.ts +42 -0
- package/src/core/host/index.ts +14 -0
- package/src/core/host/replica.ts +286 -0
- package/src/core/host/resources/attachments.ts +22 -0
- package/src/core/host/resources/document.ts +101 -0
- package/src/core/host/static.ts +77 -0
- package/src/core/host/wasm.ts +97 -0
- package/src/core/hotkeys/actions.ts +2 -4
- package/src/core/islands/__tests__/bootstrap.test.ts +17 -17
- package/src/core/islands/__tests__/bridge.test.ts +31 -26
- package/src/core/islands/bootstrap.ts +22 -51
- package/src/core/islands/bridge.ts +31 -20
- package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
- package/src/core/islands/worker/__tests__/controller.test.ts +62 -4
- package/src/core/islands/worker/controller.ts +4 -10
- package/src/core/islands/worker/worker.tsx +33 -33
- package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
- package/src/core/network/connection.ts +4 -2
- package/src/core/network/useConnectionNotice.ts +8 -2
- package/src/core/run-app.tsx +2 -2
- package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
- package/src/core/runtime/__tests__/runtime.test.ts +33 -15
- package/src/core/runtime/runtime.ts +39 -30
- package/src/core/saving/save-component.tsx +12 -3
- package/src/core/state/__tests__/jotai.test.tsx +108 -0
- package/src/core/state/jotai.ts +1 -1
- package/src/core/static/__tests__/files.test.ts +38 -10
- package/src/core/static/files.ts +2 -0
- package/src/core/wasm/__tests__/bridge.test.ts +213 -50
- package/src/core/wasm/__tests__/worker.test.ts +187 -0
- package/src/core/wasm/bridge.ts +81 -52
- package/src/core/wasm/rpc.ts +15 -3
- package/src/core/wasm/store.ts +3 -3
- package/src/core/wasm/worker/bootstrap.ts +55 -51
- package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
- package/src/core/wasm/worker/save-worker.ts +9 -10
- package/src/core/wasm/worker/types.ts +29 -18
- package/src/core/wasm/worker/worker.ts +97 -44
- package/src/core/websocket/__tests__/backend.test.ts +235 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -29
- package/src/core/websocket/__tests__/useNotebookReplica.hook.test.tsx +141 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +197 -184
- package/src/core/websocket/backend.ts +129 -0
- package/src/core/websocket/connection-state.ts +60 -0
- package/src/core/websocket/state.ts +7 -0
- package/src/core/websocket/transports/__tests__/notebook.test.ts +405 -0
- package/src/core/websocket/transports/notebook.ts +159 -0
- package/src/core/websocket/types.ts +9 -39
- package/src/core/websocket/useNotebookReplica.ts +69 -0
- package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +63 -228
- package/src/css/globals.css +5 -0
- package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
- package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
- package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
- package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
- package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
- package/src/hooks/__tests__/useScript.test.tsx +77 -0
- package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
- package/src/hooks/useAsyncData.ts +3 -3
- package/src/hooks/useDataSourceDiscovery.ts +6 -4
- package/src/hooks/useDebounce.ts +6 -10
- package/src/hooks/useDeepCompareMemoize.ts +1 -1
- package/src/hooks/useDelayElapsed.ts +14 -10
- package/src/hooks/useElapsedTime.ts +3 -3
- package/src/hooks/useEventListener.ts +1 -2
- package/src/hooks/useInputHistory.ts +4 -7
- package/src/hooks/useInternalStateWithSync.ts +6 -6
- package/src/hooks/useResizeObserver.ts +4 -7
- package/src/hooks/useScript.ts +67 -70
- package/src/hooks/useStateForKey.ts +93 -0
- package/src/mount.tsx +3 -3
- package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
- package/src/plugins/core/registerReactComponent.tsx +1 -1
- package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
- package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
- package/src/plugins/impl/DataTablePlugin.tsx +29 -33
- package/src/plugins/impl/DropdownPlugin.tsx +2 -6
- package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
- package/src/plugins/impl/FormPlugin.tsx +1 -1
- package/src/plugins/impl/RangeSliderPlugin.tsx +16 -2
- package/src/plugins/impl/RefreshPlugin.tsx +1 -0
- package/src/plugins/impl/SliderPlugin.tsx +6 -8
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +201 -1
- package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
- package/src/plugins/impl/__tests__/RangeSliderPlugin.test.tsx +109 -0
- package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
- package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
- package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
- package/src/plugins/impl/anywidget/runtime.ts +21 -9
- package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
- package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
- package/src/plugins/impl/chat/chat-ui.tsx +14 -11
- 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/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
- package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
- package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
- package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
- package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
- package/src/plugins/impl/data-frames/panel.tsx +43 -16
- package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
- package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
- package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
- package/src/plugins/impl/panel/utils.ts +17 -0
- package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
- package/src/plugins/impl/plotly/selection.ts +25 -19
- package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
- package/src/plugins/impl/vega/make-selectable.ts +4 -7
- package/src/plugins/layout/RoutesPlugin.tsx +1 -1
- package/src/stories/__tests__/editor-story.test.tsx +33 -0
- package/src/stories/button.stories.tsx +1 -0
- package/src/stories/editor.stories.tsx +24 -17
- package/src/stories/form-wrapper.stories.tsx +2 -2
- package/src/stories/select.stories.tsx +2 -2
- package/src/stories/switchable-multi-select.stories.tsx +1 -1
- package/src/utils/__tests__/arrays.test.ts +35 -0
- package/src/utils/__tests__/batch-requests.test.ts +3 -3
- package/src/utils/__tests__/dates.test.ts +116 -0
- package/src/utils/__tests__/numbers.test.ts +105 -1
- package/src/utils/__tests__/objects.test.ts +21 -0
- package/src/utils/arrays.ts +10 -0
- package/src/utils/blob.ts +1 -1
- package/src/utils/dates.ts +94 -0
- package/src/utils/numbers.ts +79 -18
- package/src/utils/objects.ts +3 -7
- package/src/utils/once.ts +19 -19
- package/src/utils/tracer.ts +9 -5
- package/src/utils/zod-utils.ts +1 -3
- package/dist/any-language-editor-B9REGlD9.js +0 -76
- package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
- package/dist/assets/worker-HPY9fbhz.js +0 -114
- package/dist/error-entries-CzoYkIQZ.js +0 -56
- package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
- package/dist/precisionRound-DMIVQrt2.js +0 -97
- package/src/core/state/__tests__/jotai.test.ts +0 -25
- package/src/core/wasm/worker/message-buffer.ts +0 -33
- package/src/core/websocket/__tests__/useWebSocket.test.ts +0 -49
- package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
- package/src/core/websocket/transports/__tests__/sse.test.ts +0 -338
- package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
- package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
- package/src/core/websocket/transports/basic.ts +0 -102
- package/src/core/websocket/transports/sse.ts +0 -308
- package/src/core/websocket/transports/transport.ts +0 -61
- package/src/core/websocket/transports/ws.ts +0 -98
- package/src/core/websocket/useWebSocket.tsx +0 -88
package/src/hooks/useDebounce.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { debounce } from "lodash-es";
|
|
3
3
|
import { useEffect, useMemo, useState } from "react";
|
|
4
4
|
import useEvent from "react-use-event-hook";
|
|
5
|
+
import { useInternalStateWithSync } from "./useInternalStateWithSync";
|
|
5
6
|
|
|
6
7
|
export function useDebounce<T>(value: T, delay: number): T {
|
|
7
8
|
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
|
@@ -41,18 +42,14 @@ export function useDebounceControlledState<T>(opts: {
|
|
|
41
42
|
disabled?: boolean;
|
|
42
43
|
}) {
|
|
43
44
|
const { initialValue, onChange, delay, disabled } = opts;
|
|
44
|
-
const [internalValue, setInternalValue] =
|
|
45
|
+
const [internalValue, setInternalValue] = useInternalStateWithSync(
|
|
46
|
+
initialValue,
|
|
47
|
+
Object.is,
|
|
48
|
+
);
|
|
45
49
|
const debouncedValue = useDebounce(internalValue, delay || 200);
|
|
46
50
|
|
|
47
51
|
const onUpdate = useEvent(onChange);
|
|
48
52
|
|
|
49
|
-
// Handle external changes:
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
// TODO: Replace this synchronization with a controlled-state primitive.
|
|
52
|
-
// oxlint-disable-next-line react/no-deriving-state-in-effects -- Synchronize the debounced draft when the controlled value changes.
|
|
53
|
-
setInternalValue(initialValue);
|
|
54
|
-
}, [initialValue]);
|
|
55
|
-
|
|
56
53
|
// Handle debounced updates for user input
|
|
57
54
|
useEffect(() => {
|
|
58
55
|
if (disabled) {
|
|
@@ -80,8 +77,7 @@ export function useDebounceControlledState<T>(opts: {
|
|
|
80
77
|
};
|
|
81
78
|
}
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
export function useDebouncedCallback<T extends (...args: any[]) => unknown>(
|
|
80
|
+
export function useDebouncedCallback<T extends (...args: never[]) => unknown>(
|
|
85
81
|
callback: T,
|
|
86
82
|
delay: number,
|
|
87
83
|
) {
|
|
@@ -12,7 +12,7 @@ import React from "react";
|
|
|
12
12
|
* Useful when the re-render if more expensive than the deep compare.
|
|
13
13
|
*/
|
|
14
14
|
export function useDeepCompareMemoize<T>(value: T) {
|
|
15
|
-
const ref = React.useRef
|
|
15
|
+
const ref = React.useRef(value);
|
|
16
16
|
|
|
17
17
|
if (!dequal(value, ref.current)) {
|
|
18
18
|
ref.current = value;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Returns `false` until `delayMs` has elapsed since mount (or since `delayMs`
|
|
@@ -8,20 +8,24 @@ import { useLayoutEffect, useState } from "react";
|
|
|
8
8
|
* `delayMs <= 0`. Useful for suppressing spinner/fallback flashes.
|
|
9
9
|
*/
|
|
10
10
|
export function useDelayElapsed(delayMs: number): boolean {
|
|
11
|
-
const [
|
|
11
|
+
const [timer, setTimer] = useState({ delayMs, elapsed: delayMs <= 0 });
|
|
12
12
|
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
// Reset during render so a changed delay never commits a stale `elapsed`
|
|
14
|
+
// (e.g. a gate re-closing shows no fallback flash before paint)
|
|
15
|
+
if (!Object.is(timer.delayMs, delayMs)) {
|
|
16
|
+
setTimer({ delayMs, elapsed: delayMs <= 0 });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
17
20
|
if (delayMs <= 0) {
|
|
18
|
-
setElapsed(true);
|
|
19
21
|
return;
|
|
20
22
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
const timeout = setTimeout(
|
|
24
|
+
() => setTimer({ delayMs, elapsed: true }),
|
|
25
|
+
delayMs,
|
|
26
|
+
);
|
|
23
27
|
return () => clearTimeout(timeout);
|
|
24
28
|
}, [delayMs]);
|
|
25
29
|
|
|
26
|
-
return elapsed;
|
|
30
|
+
return timer.elapsed;
|
|
27
31
|
}
|
|
@@ -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
|
}
|
|
@@ -62,8 +62,7 @@ export function useEventListener(
|
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
const eventListener = (event: any) => savedListener.current(event);
|
|
65
|
+
const eventListener = (event: Event) => savedListener.current(event);
|
|
67
66
|
target.addEventListener(type, eventListener, options);
|
|
68
67
|
|
|
69
68
|
return () => {
|
|
@@ -37,12 +37,9 @@ export function useInputHistory({
|
|
|
37
37
|
const historyIndexRef = React.useRef<number>(-1);
|
|
38
38
|
// Store the current input when navigating history
|
|
39
39
|
const pendingInputRef = React.useRef<string>("");
|
|
40
|
-
// Keep a ref to history for use in callbacks without causing re-renders
|
|
41
|
-
const historyRef = React.useRef(history);
|
|
42
|
-
historyRef.current = history;
|
|
43
40
|
|
|
44
41
|
const navigateUp = React.useCallback(() => {
|
|
45
|
-
const currentHistory =
|
|
42
|
+
const currentHistory = history;
|
|
46
43
|
if (currentHistory.length === 0) {
|
|
47
44
|
return;
|
|
48
45
|
}
|
|
@@ -59,10 +56,10 @@ export function useInputHistory({
|
|
|
59
56
|
historyIndexRef.current = newIndex;
|
|
60
57
|
setValue(currentHistory[currentHistory.length - 1 - newIndex]);
|
|
61
58
|
}
|
|
62
|
-
}, [value, setValue]);
|
|
59
|
+
}, [history, value, setValue]);
|
|
63
60
|
|
|
64
61
|
const navigateDown = React.useCallback(() => {
|
|
65
|
-
const currentHistory =
|
|
62
|
+
const currentHistory = history;
|
|
66
63
|
if (currentHistory.length === 0) {
|
|
67
64
|
return;
|
|
68
65
|
}
|
|
@@ -79,7 +76,7 @@ export function useInputHistory({
|
|
|
79
76
|
historyIndexRef.current = -1;
|
|
80
77
|
setValue(pendingInputRef.current);
|
|
81
78
|
}
|
|
82
|
-
}, [setValue]);
|
|
79
|
+
}, [history, setValue]);
|
|
83
80
|
|
|
84
81
|
const addToHistory = React.useCallback((command: string) => {
|
|
85
82
|
setHistory((prev) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
import {
|
|
2
|
+
import { useState } from "react";
|
|
3
3
|
|
|
4
4
|
type EqualityFn<T> = (a: T, b: T) => boolean;
|
|
5
5
|
|
|
@@ -13,13 +13,13 @@ export function useInternalStateWithSync<T>(
|
|
|
13
13
|
initial: T,
|
|
14
14
|
equalityFn: EqualityFn<T> = defaultEqualityFn,
|
|
15
15
|
) {
|
|
16
|
-
const [state, setState] = useState(initial);
|
|
17
|
-
const
|
|
16
|
+
const [state, setState] = useState(() => initial);
|
|
17
|
+
const [previousInitial, setPreviousInitial] = useState(() => initial);
|
|
18
18
|
|
|
19
19
|
// Whenever the outside state changes, update the internal state
|
|
20
|
-
if (!equalityFn(
|
|
21
|
-
setState(initial);
|
|
22
|
-
|
|
20
|
+
if (!equalityFn(previousInitial, initial)) {
|
|
21
|
+
setState(() => initial);
|
|
22
|
+
setPreviousInitial(() => initial);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
return [state, setState] as const;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type { RefObject } from "react";
|
|
8
8
|
import { useEffect, useRef } from "react";
|
|
9
|
+
import useEvent from "react-use-event-hook";
|
|
9
10
|
|
|
10
11
|
interface Size {
|
|
11
12
|
width: number | undefined;
|
|
@@ -30,9 +31,7 @@ export function useResizeObserver<T extends HTMLElement = HTMLElement>(
|
|
|
30
31
|
const { ref, box = "content-box", skip } = options;
|
|
31
32
|
// oxlint-disable-next-line react/hook-use-state
|
|
32
33
|
const previousSize = useRef<Size>({ ...initialSize });
|
|
33
|
-
const onResize =
|
|
34
|
-
// oxlint-disable-next-line react-hooks/react-compiler
|
|
35
|
-
onResize.current = options.onResize;
|
|
34
|
+
const onResize = useEvent((size: Size) => options.onResize?.(size));
|
|
36
35
|
|
|
37
36
|
useEffect(() => {
|
|
38
37
|
if (!ref.current || skip) {
|
|
@@ -63,9 +62,7 @@ export function useResizeObserver<T extends HTMLElement = HTMLElement>(
|
|
|
63
62
|
previousSize.current.width = newWidth;
|
|
64
63
|
previousSize.current.height = newHeight;
|
|
65
64
|
|
|
66
|
-
|
|
67
|
-
onResize.current(newSize);
|
|
68
|
-
}
|
|
65
|
+
onResize(newSize);
|
|
69
66
|
}
|
|
70
67
|
});
|
|
71
68
|
|
|
@@ -74,7 +71,7 @@ export function useResizeObserver<T extends HTMLElement = HTMLElement>(
|
|
|
74
71
|
return () => {
|
|
75
72
|
observer.disconnect();
|
|
76
73
|
};
|
|
77
|
-
}, [box, ref, skip]);
|
|
74
|
+
}, [box, ref, skip, onResize]);
|
|
78
75
|
}
|
|
79
76
|
|
|
80
77
|
type BoxSizesKey = keyof Pick<
|
package/src/hooks/useScript.ts
CHANGED
|
@@ -1,90 +1,87 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
import
|
|
2
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
3
3
|
|
|
4
4
|
type ScriptStatus = "loading" | "ready" | "error" | "unknown";
|
|
5
5
|
interface ScriptOptions {
|
|
6
6
|
removeOnUnmount?: boolean;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
interface ScriptEntry {
|
|
10
|
+
element: HTMLScriptElement;
|
|
11
|
+
listeners: Set<() => void>;
|
|
12
|
+
/** Whether useScript created the element, and so may remove it. */
|
|
13
|
+
owned: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
const defaultOptions = { removeOnUnmount: false };
|
|
10
17
|
|
|
18
|
+
// One entry per src, so reading the status on render is a map lookup
|
|
19
|
+
const scripts = new Map<string, ScriptEntry>();
|
|
20
|
+
|
|
21
|
+
function findScript(src: string): HTMLScriptElement | undefined {
|
|
22
|
+
// Compare attributes rather than building a selector, which `src` could break
|
|
23
|
+
return [...document.scripts].find(
|
|
24
|
+
(script) => script.getAttribute("src") === src,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function acquireScript(src: string): ScriptEntry {
|
|
29
|
+
const cached = scripts.get(src);
|
|
30
|
+
if (cached?.element.isConnected) {
|
|
31
|
+
return cached;
|
|
32
|
+
}
|
|
33
|
+
const existing = findScript(src);
|
|
34
|
+
const element = existing ?? document.createElement("script");
|
|
35
|
+
const entry: ScriptEntry = {
|
|
36
|
+
element,
|
|
37
|
+
listeners: new Set(),
|
|
38
|
+
owned: !existing,
|
|
39
|
+
};
|
|
40
|
+
const setStatus = (status: ScriptStatus) => {
|
|
41
|
+
element.dataset.status = status;
|
|
42
|
+
entry.listeners.forEach((listener) => listener());
|
|
43
|
+
};
|
|
44
|
+
// Listen for the script's lifetime rather than per subscriber, so the
|
|
45
|
+
// status still settles if the first subscriber unmounts mid-load.
|
|
46
|
+
element.addEventListener("load", () => setStatus("ready"), { once: true });
|
|
47
|
+
element.addEventListener("error", () => setStatus("error"), { once: true });
|
|
48
|
+
if (!existing) {
|
|
49
|
+
element.src = src;
|
|
50
|
+
element.async = true;
|
|
51
|
+
element.dataset.status = "loading";
|
|
52
|
+
document.body.append(element);
|
|
53
|
+
}
|
|
54
|
+
scripts.set(src, entry);
|
|
55
|
+
return entry;
|
|
56
|
+
}
|
|
57
|
+
|
|
11
58
|
export function useScript(
|
|
12
59
|
src: string,
|
|
13
60
|
options: ScriptOptions = defaultOptions,
|
|
14
61
|
) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
);
|
|
19
|
-
return (
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
React.useEffect(() => {
|
|
23
|
-
let script = document.querySelector<HTMLScriptElement>(
|
|
24
|
-
`script[src="${src}"]`,
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
const updateStatus = (newStatus: ScriptStatus) => {
|
|
28
|
-
if (script) {
|
|
29
|
-
script.dataset.status = newStatus;
|
|
30
|
-
setStatus(newStatus);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const handleScriptLoad = () => updateStatus("ready");
|
|
35
|
-
const handleScriptError = () => updateStatus("error");
|
|
36
|
-
|
|
37
|
-
// observes DOM changes and updates the status
|
|
38
|
-
const observeScript = (script: HTMLScriptElement) => {
|
|
39
|
-
const observer = new MutationObserver(() => {
|
|
40
|
-
const newStatus = script.dataset.status;
|
|
41
|
-
if (newStatus) {
|
|
42
|
-
setStatus(newStatus as ScriptStatus);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
observer.observe(script, {
|
|
46
|
-
attributes: true,
|
|
47
|
-
attributeFilter: ["data-status"],
|
|
48
|
-
});
|
|
49
|
-
return observer;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
if (!script) {
|
|
53
|
-
script = document.createElement("script");
|
|
54
|
-
script.src = src;
|
|
55
|
-
script.async = true;
|
|
56
|
-
updateStatus("loading");
|
|
57
|
-
document.body.append(script);
|
|
58
|
-
|
|
59
|
-
script.addEventListener("load", handleScriptLoad);
|
|
60
|
-
script.addEventListener("error", handleScriptError);
|
|
61
|
-
|
|
62
|
-
const observer = observeScript(script);
|
|
62
|
+
const { removeOnUnmount } = options;
|
|
63
|
+
const getSnapshot = useCallback((): ScriptStatus => {
|
|
64
|
+
const cached = scripts.get(src)?.element;
|
|
65
|
+
const element = cached?.isConnected ? cached : findScript(src);
|
|
66
|
+
return (element?.dataset.status as ScriptStatus) || "unknown";
|
|
67
|
+
}, [src]);
|
|
63
68
|
|
|
69
|
+
const subscribe = useCallback(
|
|
70
|
+
(onChange: () => void) => {
|
|
71
|
+
const entry = acquireScript(src);
|
|
72
|
+
entry.listeners.add(onChange);
|
|
64
73
|
return () => {
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (options.removeOnUnmount) {
|
|
71
|
-
script.remove();
|
|
74
|
+
entry.listeners.delete(onChange);
|
|
75
|
+
if (removeOnUnmount && entry.owned && entry.listeners.size === 0) {
|
|
76
|
+
entry.element.remove();
|
|
77
|
+
if (scripts.get(src) === entry) {
|
|
78
|
+
scripts.delete(src);
|
|
72
79
|
}
|
|
73
80
|
}
|
|
74
81
|
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (domStatus) {
|
|
79
|
-
if (domStatus === "ready" || domStatus === "error") {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
const observer = observeScript(script);
|
|
83
|
-
return () => observer.disconnect();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
setStatus("unknown");
|
|
87
|
-
}, [src, options.removeOnUnmount]);
|
|
82
|
+
},
|
|
83
|
+
[src, removeOnUnmount],
|
|
84
|
+
);
|
|
88
85
|
|
|
89
|
-
return
|
|
86
|
+
return useSyncExternalStore(subscribe, getSnapshot);
|
|
90
87
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { type SetStateAction, useCallback, useReducer } from "react";
|
|
3
|
+
|
|
4
|
+
interface KeyedState<T> {
|
|
5
|
+
key: unknown;
|
|
6
|
+
value: T;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type KeyedAction<T> =
|
|
10
|
+
| { type: "reset"; state: KeyedState<T> }
|
|
11
|
+
| { type: "update"; key: unknown; update: SetStateAction<T> };
|
|
12
|
+
|
|
13
|
+
function reduceKeyedState<T>(
|
|
14
|
+
previous: KeyedState<T>,
|
|
15
|
+
action: KeyedAction<T>,
|
|
16
|
+
): KeyedState<T> {
|
|
17
|
+
if (action.type === "reset") {
|
|
18
|
+
return action.state;
|
|
19
|
+
}
|
|
20
|
+
if (!Object.is(previous.key, action.key)) {
|
|
21
|
+
return previous;
|
|
22
|
+
}
|
|
23
|
+
const value =
|
|
24
|
+
typeof action.update === "function"
|
|
25
|
+
? (action.update as (previous: T) => T)(previous.value)
|
|
26
|
+
: action.update;
|
|
27
|
+
return Object.is(previous.value, value)
|
|
28
|
+
? previous
|
|
29
|
+
: { key: action.key, value };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Local state that resets when its owner key changes.
|
|
34
|
+
*
|
|
35
|
+
* The key uses Object.is. NaN matches NaN, 0 differs from -0, and objects match
|
|
36
|
+
* by reference. Keep the key stable between resets. An inline object such as
|
|
37
|
+
* `{ id }` creates a new key on each render and causes a render loop.
|
|
38
|
+
*
|
|
39
|
+
* On a key change, this hook calls initialize() and schedules a state update
|
|
40
|
+
* during render. React retries the calling component before rendering its
|
|
41
|
+
* children. Children and effects receive the reset value without an intervening
|
|
42
|
+
* commit of the old value. This resets only this hook's state. The component
|
|
43
|
+
* stays mounted, and its other hooks keep their state.
|
|
44
|
+
*
|
|
45
|
+
* initialize must be pure. React can call it more than once in Strict Mode or
|
|
46
|
+
* when retrying an interrupted render. A new initializer alone does not reset
|
|
47
|
+
* state. The next key change uses the initializer from that render. After
|
|
48
|
+
* committed key changes A -> B -> A, A starts fresh instead of restoring its
|
|
49
|
+
* earlier value. If React abandons a pending render for B, the visible state
|
|
50
|
+
* for A remains intact, including edits made while B was pending.
|
|
51
|
+
*
|
|
52
|
+
* The setter accepts values or pure updater functions, like useState. To store
|
|
53
|
+
* a function, use setValue(() => callback). Its identity is stable for the same
|
|
54
|
+
* key and changes with the key. Updates from a different key are ignored,
|
|
55
|
+
* including their updater functions. After A -> B -> A, an old setter for A
|
|
56
|
+
* can update A again. This hook does not cancel requests. For request results,
|
|
57
|
+
* use a distinct key for each lifetime so late results stay invalid.
|
|
58
|
+
*
|
|
59
|
+
* React can still call the component for ignored or same-value updates.
|
|
60
|
+
* Render counts do not indicate how many changes React commits to the screen.
|
|
61
|
+
*
|
|
62
|
+
* Use useInternalStateWithSync when the external value itself supplies the
|
|
63
|
+
* reset value. Use this hook when a separate key controls the reset.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* const [expanded, setExpanded] = useStateForKey(tree, () => new Set<string>());
|
|
67
|
+
*
|
|
68
|
+
* @see https://react.dev/reference/react/useState#storing-information-from-previous-renders
|
|
69
|
+
*/
|
|
70
|
+
export function useStateForKey<T>(key: unknown, initialize: () => T) {
|
|
71
|
+
// useState can eagerly evaluate an updater against a suspended render's
|
|
72
|
+
// state. A reducer checks the key when React processes each render's queue,
|
|
73
|
+
// so the still-visible owner can accept edits during a transition.
|
|
74
|
+
const [stored, dispatch] = useReducer(reduceKeyedState<T>, key, (key) => ({
|
|
75
|
+
key,
|
|
76
|
+
value: initialize(),
|
|
77
|
+
}));
|
|
78
|
+
const current = Object.is(stored.key, key)
|
|
79
|
+
? stored
|
|
80
|
+
: { key, value: initialize() };
|
|
81
|
+
if (current !== stored) {
|
|
82
|
+
dispatch({ type: "reset", state: current });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const setValue = useCallback(
|
|
86
|
+
(update: SetStateAction<T>) => {
|
|
87
|
+
dispatch({ type: "update", key, update });
|
|
88
|
+
},
|
|
89
|
+
[key],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return [current.value, setValue] as const;
|
|
93
|
+
}
|
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
|
*/
|
|
@@ -347,7 +347,7 @@ function initStore(options: unknown) {
|
|
|
347
347
|
);
|
|
348
348
|
store.set(userConfigAtom, parseUserConfig(parsedOptions.data.config));
|
|
349
349
|
store.set(appConfigAtom, parseAppConfig(parsedOptions.data.appConfig));
|
|
350
|
-
store.set(
|
|
350
|
+
store.set(pairConfigAtom, parsedOptions.data.pairConfig);
|
|
351
351
|
|
|
352
352
|
// Runtime config
|
|
353
353
|
if (parsedOptions.data.runtimeConfig.length > 0) {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { DataEditorPlugin } from "../impl/DataEditorPlugin";
|
|
5
|
+
import { DataTablePlugin } from "../impl/DataTablePlugin";
|
|
6
|
+
import { DataFramePlugin } from "../impl/data-frames/DataFramePlugin";
|
|
7
|
+
import { PlotlyPlugin } from "../impl/plotly/PlotlyPlugin";
|
|
8
|
+
import { VegaPlugin } from "../impl/vega/VegaPlugin";
|
|
9
|
+
|
|
10
|
+
describe("plugin object schemas", () => {
|
|
11
|
+
const rows = [{ value: 1, extra: { nested: true } }];
|
|
12
|
+
const styles = { "0": { value: { color: "red", customProperty: 1 } } };
|
|
13
|
+
const tableData = {
|
|
14
|
+
initialValue: [],
|
|
15
|
+
label: null,
|
|
16
|
+
data: rows,
|
|
17
|
+
totalRows: 1,
|
|
18
|
+
totalColumns: 1,
|
|
19
|
+
rowHeaders: [],
|
|
20
|
+
cellStyles: styles,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
it("preserves table rows and arbitrary cell style fields", () => {
|
|
24
|
+
expect(DataTablePlugin.validator.parse(tableData)).toMatchObject({
|
|
25
|
+
data: rows,
|
|
26
|
+
cellStyles: styles,
|
|
27
|
+
pageSize: 10,
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("preserves data editor row fields", () => {
|
|
32
|
+
expect(
|
|
33
|
+
DataEditorPlugin.validator.parse({
|
|
34
|
+
initialValue: { edits: [] },
|
|
35
|
+
label: null,
|
|
36
|
+
data: rows,
|
|
37
|
+
editableColumns: "all",
|
|
38
|
+
}),
|
|
39
|
+
).toMatchObject({ data: rows, columnNames: [] });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("preserves table RPC payload fields", () => {
|
|
43
|
+
const functions = DataTablePlugin.functions!;
|
|
44
|
+
const searchResult = {
|
|
45
|
+
data: rows,
|
|
46
|
+
total_rows: 1,
|
|
47
|
+
cell_styles: styles,
|
|
48
|
+
cell_hover_texts: null,
|
|
49
|
+
};
|
|
50
|
+
expect(functions.search.output.parse(searchResult)).toEqual(searchResult);
|
|
51
|
+
expect(functions.get_row_ids!.input.parse({ custom: true })).toEqual({
|
|
52
|
+
custom: true,
|
|
53
|
+
});
|
|
54
|
+
expect(functions.get_data_url!.input.parse({ custom: true })).toEqual({
|
|
55
|
+
custom: true,
|
|
56
|
+
});
|
|
57
|
+
expect(
|
|
58
|
+
functions.get_data_url!.output.parse({ data_url: rows, format: "json" }),
|
|
59
|
+
).toEqual({ data_url: rows, format: "json" });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("preserves dataframe search row fields", () => {
|
|
63
|
+
const result = { data: rows, total_rows: 1 };
|
|
64
|
+
expect(DataFramePlugin.functions!.search.output.parse(result)).toEqual(
|
|
65
|
+
result,
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("preserves Plotly figure and config fields", () => {
|
|
70
|
+
const data = {
|
|
71
|
+
figure: { data: [], layout: { title: "Chart" } },
|
|
72
|
+
config: { responsive: true },
|
|
73
|
+
};
|
|
74
|
+
expect(new PlotlyPlugin().validator.parse(data)).toEqual(data);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("preserves Vega spec and embed options fields and defaults", () => {
|
|
78
|
+
const spec = { mark: "bar", encoding: { x: { field: "value" } } };
|
|
79
|
+
const validator = new VegaPlugin().validator;
|
|
80
|
+
expect(validator.parse({ spec })).toEqual({
|
|
81
|
+
spec,
|
|
82
|
+
chartSelection: true,
|
|
83
|
+
fieldSelection: true,
|
|
84
|
+
embedOptions: {},
|
|
85
|
+
});
|
|
86
|
+
expect(
|
|
87
|
+
validator.parse({ spec, embedOptions: { actions: false } }).embedOptions,
|
|
88
|
+
).toEqual({ actions: false });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("rejects scalar values where plugin objects are required", () => {
|
|
92
|
+
expect(
|
|
93
|
+
DataTablePlugin.validator.safeParse({ ...tableData, data: [42] }).success,
|
|
94
|
+
).toBe(false);
|
|
95
|
+
expect(
|
|
96
|
+
new PlotlyPlugin().validator.safeParse({ figure: 42, config: {} })
|
|
97
|
+
.success,
|
|
98
|
+
).toBe(false);
|
|
99
|
+
expect(
|
|
100
|
+
new VegaPlugin().validator.safeParse({ spec: {}, embedOptions: 42 })
|
|
101
|
+
.success,
|
|
102
|
+
).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -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(() => {
|
|
@@ -71,7 +71,7 @@ const CodeEditorComponent = (props: CodeEditorComponentProps) => {
|
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
const handleChange = useEvent((newValue: string) => {
|
|
74
|
-
setLocalValue(
|
|
74
|
+
setLocalValue(newValue);
|
|
75
75
|
if (typeof props.debounce === "number") {
|
|
76
76
|
setValueDebounced(newValue);
|
|
77
77
|
} else if (!props.debounce) {
|
|
@@ -44,7 +44,7 @@ export const DataEditorPlugin = createPlugin<Edits>("marimo-data-editor", {
|
|
|
44
44
|
),
|
|
45
45
|
}),
|
|
46
46
|
label: z.string().nullable(),
|
|
47
|
-
data: z.union([z.string(), z.array(z.
|
|
47
|
+
data: z.union([z.string(), z.array(z.looseObject({}))]),
|
|
48
48
|
fieldTypes: columnToFieldTypesSchema.nullish(),
|
|
49
49
|
columnNames: z.array(z.string()).default([]),
|
|
50
50
|
editableColumns: z.union([z.array(z.string()), z.literal("all")]),
|