@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.
Files changed (472) hide show
  1. package/dist/{ConnectedDataExplorerComponent-CNSxdKyJ.js → ConnectedDataExplorerComponent-BAiSVaHj.js} +17 -15
  2. package/dist/{ErrorBoundary-Cg59O3xL.js → ErrorBoundary-BRBKqg2U.js} +16 -16
  3. package/dist/{abnfDiagram-VCTEODGH-DQDjNXt3.js → abnfDiagram-VCTEODGH-BBHN92-F.js} +2 -2
  4. package/dist/any-language-editor-3E24SnCV.js +75 -0
  5. package/dist/{architectureDiagram-5GKGNRK7-Nmls7Wxn.js → architectureDiagram-5GKGNRK7-C0Rd64qh.js} +1 -1
  6. package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
  7. package/dist/assets/__vite-browser-external-BgQNn4dS.js +1 -0
  8. package/dist/assets/worker-FKe3RHT4.js +113 -0
  9. package/dist/{button-CvjkwdSk.js → button-BQRdqQi1.js} +199 -192
  10. package/dist/{capabilities-CQ5IjhzL.js → capabilities-CI4xUPsp.js} +2 -2
  11. package/dist/{chat-ui-BDpymeoe.js → chat-ui-CvB8Map3.js} +1195 -1195
  12. package/dist/{check-EDwSwP89.js → check-SpOMoCLP.js} +1 -1
  13. package/dist/{chunk-IMKFNOWR-CoIFEo-1.js → chunk-IMKFNOWR-hJXey5p2.js} +1 -1
  14. package/dist/{chunk-SHT3W25Y-lHiGx3ZC.js → chunk-SHT3W25Y-JMzTmmVW.js} +1 -1
  15. package/dist/{chunk-SVP7TREG-BvsHIrY_.js → chunk-SVP7TREG-BIyoHTXi.js} +1 -1
  16. package/dist/{chunk-TICWLB2K-BC8YIm_n.js → chunk-TICWLB2K-CCzH2Uwa.js} +1 -1
  17. package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
  18. package/dist/{classDiagram-ZZMXUADV-Bt98lkkv.js → classDiagram-ZZMXUADV-DB1kOPS6.js} +2 -2
  19. package/dist/{classDiagram-v2-VYDZK3BY-D9bfbcC3.js → classDiagram-v2-VYDZK3BY-CPaFL0OB.js} +2 -2
  20. package/dist/{code-visibility-Da3iitn_.js → code-visibility-CAgcNzoX.js} +17 -17
  21. package/dist/{common-C6FHcu5p.js → common-BovIV1xq.js} +6341 -5870
  22. package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-C1trdvVt.js} +3 -3
  23. package/dist/{cynefinDiagram-5FMLGOSQ-CgWra7wt.js → cynefinDiagram-5FMLGOSQ-CF93uliH.js} +1 -1
  24. package/dist/{dagre-GXQ25YYZ-CFZ8-35c.js → dagre-GXQ25YYZ-BBB-_p9D.js} +1 -1
  25. package/dist/{data-grid-overlay-editor-mJt3Pg4S.js → data-grid-overlay-editor-CBsr8Yo0.js} +5 -5
  26. package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-0PJ-9N5n.js} +1 -1
  27. package/dist/{diagram-S7CK7UJ4-lCNQrW7d.js → diagram-S7CK7UJ4-BynCnbyt.js} +1 -1
  28. package/dist/{diagram-UQ7AKVKN-CQPQ6pP_.js → diagram-UQ7AKVKN-svOxikMa.js} +1 -1
  29. package/dist/{diagram-VX7I27RA-Cf3b0llz.js → diagram-VX7I27RA-BVQSnPx0.js} +1 -1
  30. package/dist/{diagram-Z3DM3KII-Bt4QLrpn.js → diagram-Z3DM3KII-nR1J0BH5.js} +1 -1
  31. package/dist/{dialog-CEBFZNyc.js → dialog-DgeqyrEZ.js} +3 -3
  32. package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
  33. package/dist/{dropdown-menu-CP_qCYHC.js → dropdown-menu-GWcGCcxE.js} +1277 -1229
  34. package/dist/{ebnfDiagram-PWID7BFC-D3NAyPFY.js → ebnfDiagram-PWID7BFC-C9tq9-Qo.js} +2 -2
  35. package/dist/{erDiagram-RLTQ6QDP-Tlu09_Nm.js → erDiagram-RLTQ6QDP-BiaeB7Fi.js} +1 -1
  36. package/dist/{error-banner-Da600cwz.js → error-banner-D40pmfQQ.js} +86 -81
  37. package/dist/{cells-C8iCYvLU.js → error-entries-CmFDQ9CA.js} +1718 -1621
  38. package/dist/{feedback-button-DaPwHIdM.js → feedback-button-B_9o6DdV.js} +122 -123
  39. package/dist/{flowDiagram-HODETNUW-CAYbpJmV.js → flowDiagram-HODETNUW-f-sLm87k.js} +2 -2
  40. package/dist/{formats-BGh2thRc.js → formats-CCxaLPNs.js} +3 -3
  41. package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
  42. package/dist/{glide-data-editor-Ctz2mH6k.js → glide-data-editor-i7BEAfhs.js} +370 -368
  43. package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Dz_F4pAY.js} +16 -16
  44. package/dist/{globals-EpFoRj_E.js → globals-BDzA_Re9.js} +3 -3
  45. package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
  46. package/dist/{html-to-image-CZY1pIZH.js → html-to-image-BrC8OJcj.js} +634 -597
  47. package/dist/{infoDiagram-27XIBGKW-XrDxhr0x.js → infoDiagram-27XIBGKW-BHrWywBe.js} +1 -1
  48. package/dist/{ishikawaDiagram-5VMMS53U-CD1muzov.js → ishikawaDiagram-5VMMS53U-Da4s0Reg.js} +1 -1
  49. package/dist/{kanban-definition-UXKFOSKX-Dg9W142_.js → kanban-definition-UXKFOSKX-DDf1GVLj.js} +1 -1
  50. package/dist/{label-BDKF3Afc.js → label-D9_-zSg-.js} +3 -3
  51. package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
  52. package/dist/{loader-BQOfjTIJ.js → loader-DOVB0Syl.js} +12 -12
  53. package/dist/main.js +7548 -7226
  54. package/dist/{mermaid-CiAXjLpA.js → mermaid-ClwTt7OD.js} +38 -38
  55. package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
  56. package/dist/{mindmap-definition-YA3MSWOX-DgCidgYJ.js → mindmap-definition-YA3MSWOX-D60oMZtT.js} +1 -1
  57. package/dist/{number-overlay-editor-CtxrummL.js → number-overlay-editor-D9WjQlOL.js} +3 -3
  58. package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
  59. package/dist/{once-BtE-wKRL.js → once-eG8Vmvjd.js} +1 -1
  60. package/dist/{pegDiagram-XKGWAZYB-coOYYKB6.js → pegDiagram-XKGWAZYB-B9wFpMga.js} +2 -2
  61. package/dist/{pieDiagram-E7YTZNPT-BnXTc3bj.js → pieDiagram-E7YTZNPT-BZwvMuBT.js} +1 -1
  62. package/dist/precisionRound-DhAzeRcm.js +49 -0
  63. package/dist/{process-output-e4jj9Ijt.js → process-output-Bw-vzyoy.js} +4 -4
  64. package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
  65. package/dist/{railroadDiagram-O6MQD6OU-B28-u83n.js → railroadDiagram-O6MQD6OU-DqhZGsN2.js} +2 -2
  66. package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
  67. package/dist/{requirementDiagram-BXWQKSXE-CdYCTMu0.js → requirementDiagram-BXWQKSXE-CL5dYARV.js} +1 -1
  68. package/dist/{reveal-component-DrIXvR_7.js → reveal-component-BxP2jlPx.js} +14 -14
  69. package/dist/{select-BButFi-d.js → select-CkF_GBxO.js} +537 -458
  70. package/dist/{sequenceDiagram-WJ2MYXX4-CM3aRAe_.js → sequenceDiagram-WJ2MYXX4-BSUCRd2x.js} +1 -1
  71. package/dist/{spec-BwarBpUJ.js → spec-DiSh_qJC.js} +41 -11
  72. package/dist/{stateDiagram-D77RDMKH-DOZUMVj8.js → stateDiagram-D77RDMKH-B-1Hvyb0.js} +2 -2
  73. package/dist/{stateDiagram-v2-MP3YSRHH-CISYxma6.js → stateDiagram-v2-MP3YSRHH--HtiR4yB.js} +2 -2
  74. package/dist/style.css +1 -1
  75. package/dist/{swimlanes-42K2YHIH-BkSppxp8.js → swimlanes-42K2YHIH-Bdfhrd-2.js} +1 -1
  76. package/dist/{swimlanesDiagram-VR7AAH4N-c8G5pGxC.js → swimlanesDiagram-VR7AAH4N-BJIfxSz0.js} +2 -2
  77. package/dist/{swiper-component-DKntyn8p.js → swiper-component-BkCm-kLg.js} +2 -2
  78. package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
  79. package/dist/{timeline-definition-24CTP7MA-CaMQayqK.js → timeline-definition-24CTP7MA-Cs4EfFD3.js} +1 -1
  80. package/dist/{toDate-BNJf01u1.js → toDate-DGvRHwTk.js} +57 -54
  81. package/dist/{toast-B9UMEi8u.js → toast-Btbve3Lm.js} +231 -213
  82. package/dist/{tooltip-BO1REbqs.js → tooltip-CJ4Rqasp.js} +327 -303
  83. package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-CmGwRNId.js} +1482 -1433
  84. package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-BmVgnMP5.js} +15 -14
  85. package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-eCL_SdeG.js} +2 -2
  86. package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-Ch_AeGu6.js} +1 -1
  87. package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-CSdWn6NP.js} +1 -1
  88. package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-B5BX921H.js} +3 -3
  89. package/dist/{useTheme-CRnjkku0.js → useTheme-Bq9pi_vz.js} +6 -6
  90. package/dist/{vega-component-DG29TAB7.js → vega-component-BgyZ-COt.js} +147 -147
  91. package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
  92. package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
  93. package/dist/{vennDiagram-4TSXK5OY-CF8pfaK3.js → vennDiagram-4TSXK5OY-DRu0vRjO.js} +1 -1
  94. package/dist/{wardleyDiagram-VM6X3IG4-dGPu39d-.js → wardleyDiagram-VM6X3IG4-DzbDxwDY.js} +1 -1
  95. package/dist/{xychartDiagram-S5SC5T6Z-CP6-9q56.js → xychartDiagram-S5SC5T6Z-CLWj6ZFQ.js} +2 -2
  96. package/package.json +15 -12
  97. package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
  98. package/src/__tests__/host-imports.test.ts +101 -0
  99. package/src/__tests__/module-entry.test.ts +32 -0
  100. package/src/__tests__/mount.test.ts +8 -8
  101. package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
  102. package/src/components/ai/ai-model-dropdown.tsx +11 -6
  103. package/src/components/app-config/ai-config.tsx +7 -2
  104. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  105. package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
  106. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  107. package/src/components/chat/acp/blocks.tsx +3 -2
  108. package/src/components/chat/acp/session-tabs.tsx +8 -3
  109. package/src/components/chat/chat-components.tsx +2 -0
  110. package/src/components/chat/chat-panel.tsx +9 -8
  111. package/src/components/chat/chat-utils.ts +4 -8
  112. package/src/components/data-table/TableTopBar.tsx +2 -0
  113. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  114. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  115. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  116. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  117. package/src/components/data-table/__tests__/context-menu.test.ts +176 -0
  118. package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
  119. package/src/components/data-table/__tests__/date-filter-inputs.test.tsx +24 -1
  120. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  121. package/src/components/data-table/__tests__/export-actions.test.tsx +434 -6
  122. package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
  123. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  124. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  125. package/src/components/data-table/cell-selection/feature.ts +2 -2
  126. package/src/components/data-table/cell-selection/types.ts +2 -2
  127. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  128. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  129. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  130. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  131. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  132. package/src/components/data-table/charts/charts.tsx +40 -30
  133. package/src/components/data-table/charts/storage.ts +16 -5
  134. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  135. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  136. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  137. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  138. package/src/components/data-table/columns.tsx +45 -46
  139. package/src/components/data-table/context-menu.tsx +161 -46
  140. package/src/components/data-table/data-table.tsx +13 -1
  141. package/src/components/data-table/date-filter-inputs.tsx +15 -42
  142. package/src/components/data-table/date-popover.tsx +6 -4
  143. package/src/components/data-table/export-actions.tsx +267 -163
  144. package/src/components/data-table/range-focus/atoms.ts +4 -6
  145. package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
  146. package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
  147. package/src/components/data-table/renderers.tsx +16 -7
  148. package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
  149. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
  150. package/src/components/data-table/schemas.ts +14 -36
  151. package/src/components/data-table/search-highlight.tsx +79 -0
  152. package/src/components/data-table/types.ts +1 -0
  153. package/src/components/data-table/url-detector.tsx +49 -9
  154. package/src/components/data-table/utils.ts +18 -0
  155. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  156. package/src/components/datasources/__tests__/column-preview.test.tsx +98 -5
  157. package/src/components/datasources/__tests__/datasources-interactions.test.tsx +181 -0
  158. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  159. package/src/components/datasources/__tests__/utils.test.ts +57 -6
  160. package/src/components/datasources/column-preview.tsx +13 -4
  161. package/src/components/datasources/datasources.tsx +51 -39
  162. package/src/components/datasources/utils.ts +32 -23
  163. package/src/components/debugger/debugger-code.tsx +5 -0
  164. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  165. package/src/components/dependency-graph/elements.ts +24 -21
  166. package/src/components/dependency-graph/panels.tsx +2 -1
  167. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  168. package/src/components/editor/Output.tsx +9 -2
  169. package/src/components/editor/SortableCell.tsx +1 -3
  170. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  171. package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
  172. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
  173. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +210 -34
  174. package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
  175. package/src/components/editor/actions/pair-with-agent-modal.tsx +100 -37
  176. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  177. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  178. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  179. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  180. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  181. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  182. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  183. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  184. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  185. package/src/components/editor/cell/collapse.tsx +5 -3
  186. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  187. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  188. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  189. package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
  190. package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
  191. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
  192. package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
  193. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  194. package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
  195. package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -1
  196. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +366 -0
  197. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  198. package/src/components/editor/chrome/wrapper/footer-item.tsx +32 -24
  199. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +237 -0
  200. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  201. package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
  202. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  203. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  204. package/src/components/editor/connections/components.tsx +1 -1
  205. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  206. package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
  207. package/src/components/editor/controls/command-palette.tsx +22 -10
  208. package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
  209. package/src/components/editor/dynamic-favicon.tsx +21 -40
  210. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +537 -0
  211. package/src/components/editor/errors/send-error-report-button.tsx +69 -0
  212. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  213. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
  214. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  215. package/src/components/editor/file-tree/file-explorer.tsx +45 -8
  216. package/src/components/editor/file-tree/file-name-input.tsx +1 -0
  217. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  218. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  219. package/src/components/editor/header/status.tsx +28 -5
  220. package/src/components/editor/links/__tests__/cell-link.test.tsx +146 -0
  221. package/src/components/editor/links/cell-link.tsx +14 -5
  222. package/src/components/editor/navigation/focus-utils.ts +4 -4
  223. package/src/components/editor/notebook-banner.tsx +1 -0
  224. package/src/components/editor/notebook-cell.tsx +11 -2
  225. package/src/components/editor/output/MarimoErrorOutput.tsx +21 -5
  226. package/src/components/editor/output/MarimoTracebackOutput.tsx +54 -37
  227. package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
  228. package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
  229. package/src/components/editor/output/ansi-reduce.ts +36 -17
  230. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  231. package/src/components/editor/package-alert.tsx +14 -7
  232. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
  233. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  234. package/src/components/home/components.tsx +17 -31
  235. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  236. package/src/components/icons/large-spinner.tsx +3 -14
  237. package/src/components/icons/spinner.tsx +1 -1
  238. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  239. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  240. package/src/components/pages/home-page.tsx +164 -91
  241. package/src/components/scratchpad/scratchpad.test.tsx +95 -0
  242. package/src/components/scratchpad/scratchpad.tsx +16 -3
  243. package/src/components/slides/reveal-component.tsx +4 -2
  244. package/src/components/slides/slide-cell-view.tsx +1 -1
  245. package/src/components/slides/slide-form.tsx +1 -0
  246. package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
  247. package/src/components/tracing/tracing.tsx +45 -41
  248. package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
  249. package/src/components/ui/__tests__/range-slider.test.tsx +165 -0
  250. package/src/components/ui/alert-dialog.tsx +1 -1
  251. package/src/components/ui/draggable-popover.tsx +24 -2
  252. package/src/components/ui/range-slider.tsx +22 -9
  253. package/src/core/ai/context/providers/datasource.ts +6 -2
  254. package/src/core/ai/tools/base.ts +16 -7
  255. package/src/core/ai/tools/edit-notebook-tool.ts +47 -45
  256. package/src/core/ai/tools/registry.ts +13 -8
  257. package/src/core/ai/tools/sample-tool.ts +6 -7
  258. package/src/core/attachments/__tests__/toast.test.tsx +58 -0
  259. package/src/core/attachments/state.ts +18 -0
  260. package/src/core/attachments/toast.ts +15 -0
  261. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  262. package/src/core/cells/__tests__/cell.test.ts +39 -0
  263. package/src/core/cells/__tests__/cells.test.ts +55 -1
  264. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  265. package/src/core/cells/__tests__/error-report.test.ts +139 -0
  266. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  267. package/src/core/cells/__tests__/session.test.ts +167 -37
  268. package/src/core/cells/cell.ts +14 -3
  269. package/src/core/cells/cells.ts +3 -1
  270. package/src/core/cells/error-report.ts +63 -0
  271. package/src/core/cells/pending-delete-service.ts +2 -6
  272. package/src/core/cells/session.ts +29 -2
  273. package/src/core/cells/types.ts +10 -1
  274. package/src/core/codemirror/__tests__/setup.test.ts +29 -29
  275. package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -0
  276. package/src/core/codemirror/cells/extensions.ts +7 -3
  277. package/src/core/codemirror/keymaps/vim.ts +2 -6
  278. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  279. package/src/core/codemirror/language/extension.ts +8 -8
  280. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  281. package/src/core/codemirror/language/languages/python.ts +16 -10
  282. package/src/core/codemirror/language/languages/sql/sql.ts +9 -7
  283. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  284. package/src/core/codemirror/language/types.ts +7 -7
  285. package/src/core/codemirror/rtc/extension.ts +24 -12
  286. package/src/core/codemirror/rtc/loro/awareness.ts +34 -19
  287. package/src/core/codemirror/rtc/loro/sync.ts +1 -0
  288. package/src/core/codemirror/utils.ts +1 -0
  289. package/src/core/config/pair.ts +5 -3
  290. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  291. package/src/core/datasets/data-source-discovery.ts +4 -6
  292. package/src/core/document/__tests__/codemirror.test.ts +485 -0
  293. package/src/core/document/__tests__/draft.test.ts +322 -0
  294. package/src/core/document/__tests__/fixtures.ts +92 -0
  295. package/src/core/document/__tests__/hooks.test.tsx +386 -0
  296. package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  297. package/src/core/document/__tests__/model.test.ts +191 -0
  298. package/src/core/document/__tests__/protocol-fixtures.ts +38 -0
  299. package/src/core/document/__tests__/recovery-scenarios.test.ts +230 -0
  300. package/src/core/document/__tests__/scenario-fixture.ts +43 -0
  301. package/src/core/document/__tests__/scenarios.test.ts +205 -0
  302. package/src/core/document/__tests__/state.test.ts +438 -0
  303. package/src/core/document/__tests__/submission.test.ts +203 -0
  304. package/src/core/document/__tests__/test-host.test.ts +92 -0
  305. package/src/core/document/__tests__/test-host.ts +247 -0
  306. package/src/core/document/__tests__/test-notebook.ts +149 -0
  307. package/src/core/document/__tests__/user.ts +61 -0
  308. package/src/core/document/codemirror.ts +311 -0
  309. package/src/core/document/draft.ts +472 -0
  310. package/src/core/document/hooks.tsx +118 -0
  311. package/src/core/document/index.ts +15 -0
  312. package/src/core/document/model.ts +55 -0
  313. package/src/core/document/select.ts +41 -0
  314. package/src/core/document/state.ts +374 -0
  315. package/src/core/document/sync.ts +369 -0
  316. package/src/core/edit-app.tsx +2 -2
  317. package/src/core/host/AGENTS.md +12 -0
  318. package/src/core/host/__tests__/attachments.test.ts +56 -0
  319. package/src/core/host/__tests__/document.test.ts +126 -0
  320. package/src/core/host/__tests__/fixtures.ts +79 -0
  321. package/src/core/host/__tests__/replica.test.ts +550 -0
  322. package/src/core/host/__tests__/static.test.ts +175 -0
  323. package/src/core/host/__tests__/wasm.test.ts +166 -0
  324. package/src/core/host/backend.ts +19 -0
  325. package/src/core/host/connection.ts +42 -0
  326. package/src/core/host/index.ts +14 -0
  327. package/src/core/host/replica.ts +286 -0
  328. package/src/core/host/resources/attachments.ts +22 -0
  329. package/src/core/host/resources/document.ts +101 -0
  330. package/src/core/host/static.ts +77 -0
  331. package/src/core/host/wasm.ts +97 -0
  332. package/src/core/hotkeys/actions.ts +2 -4
  333. package/src/core/islands/__tests__/bootstrap.test.ts +17 -17
  334. package/src/core/islands/__tests__/bridge.test.ts +31 -26
  335. package/src/core/islands/bootstrap.ts +22 -51
  336. package/src/core/islands/bridge.ts +31 -20
  337. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  338. package/src/core/islands/worker/__tests__/controller.test.ts +62 -4
  339. package/src/core/islands/worker/controller.ts +4 -10
  340. package/src/core/islands/worker/worker.tsx +33 -33
  341. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  342. package/src/core/network/connection.ts +4 -2
  343. package/src/core/network/useConnectionNotice.ts +8 -2
  344. package/src/core/run-app.tsx +2 -2
  345. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  346. package/src/core/runtime/__tests__/runtime.test.ts +33 -15
  347. package/src/core/runtime/runtime.ts +39 -30
  348. package/src/core/saving/save-component.tsx +12 -3
  349. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  350. package/src/core/state/jotai.ts +1 -1
  351. package/src/core/static/__tests__/files.test.ts +38 -10
  352. package/src/core/static/files.ts +2 -0
  353. package/src/core/wasm/__tests__/bridge.test.ts +213 -50
  354. package/src/core/wasm/__tests__/worker.test.ts +187 -0
  355. package/src/core/wasm/bridge.ts +81 -52
  356. package/src/core/wasm/rpc.ts +15 -3
  357. package/src/core/wasm/store.ts +3 -3
  358. package/src/core/wasm/worker/bootstrap.ts +55 -51
  359. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  360. package/src/core/wasm/worker/save-worker.ts +9 -10
  361. package/src/core/wasm/worker/types.ts +29 -18
  362. package/src/core/wasm/worker/worker.ts +97 -44
  363. package/src/core/websocket/__tests__/backend.test.ts +235 -0
  364. package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -29
  365. package/src/core/websocket/__tests__/useNotebookReplica.hook.test.tsx +141 -0
  366. package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +197 -184
  367. package/src/core/websocket/backend.ts +129 -0
  368. package/src/core/websocket/connection-state.ts +60 -0
  369. package/src/core/websocket/state.ts +7 -0
  370. package/src/core/websocket/transports/__tests__/notebook.test.ts +405 -0
  371. package/src/core/websocket/transports/notebook.ts +159 -0
  372. package/src/core/websocket/types.ts +9 -39
  373. package/src/core/websocket/useNotebookReplica.ts +69 -0
  374. package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +63 -228
  375. package/src/css/globals.css +5 -0
  376. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  377. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  378. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  379. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  380. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  381. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  382. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  383. package/src/hooks/useAsyncData.ts +3 -3
  384. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  385. package/src/hooks/useDebounce.ts +6 -10
  386. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  387. package/src/hooks/useDelayElapsed.ts +14 -10
  388. package/src/hooks/useElapsedTime.ts +3 -3
  389. package/src/hooks/useEventListener.ts +1 -2
  390. package/src/hooks/useInputHistory.ts +4 -7
  391. package/src/hooks/useInternalStateWithSync.ts +6 -6
  392. package/src/hooks/useResizeObserver.ts +4 -7
  393. package/src/hooks/useScript.ts +67 -70
  394. package/src/hooks/useStateForKey.ts +93 -0
  395. package/src/mount.tsx +3 -3
  396. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  397. package/src/plugins/core/registerReactComponent.tsx +1 -1
  398. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  399. package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
  400. package/src/plugins/impl/DataTablePlugin.tsx +29 -33
  401. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  402. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  403. package/src/plugins/impl/FormPlugin.tsx +1 -1
  404. package/src/plugins/impl/RangeSliderPlugin.tsx +16 -2
  405. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  406. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  407. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +201 -1
  408. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  409. package/src/plugins/impl/__tests__/RangeSliderPlugin.test.tsx +109 -0
  410. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  411. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  412. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  413. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  414. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  415. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  416. package/src/plugins/impl/chat/chat-ui.tsx +14 -11
  417. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  418. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  419. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  420. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  421. package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
  422. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  423. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  424. package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
  425. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
  426. package/src/plugins/impl/data-frames/panel.tsx +43 -16
  427. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  428. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  429. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  430. package/src/plugins/impl/panel/utils.ts +17 -0
  431. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  432. package/src/plugins/impl/plotly/selection.ts +25 -19
  433. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  434. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  435. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  436. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  437. package/src/stories/button.stories.tsx +1 -0
  438. package/src/stories/editor.stories.tsx +24 -17
  439. package/src/stories/form-wrapper.stories.tsx +2 -2
  440. package/src/stories/select.stories.tsx +2 -2
  441. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  442. package/src/utils/__tests__/arrays.test.ts +35 -0
  443. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  444. package/src/utils/__tests__/dates.test.ts +116 -0
  445. package/src/utils/__tests__/numbers.test.ts +105 -1
  446. package/src/utils/__tests__/objects.test.ts +21 -0
  447. package/src/utils/arrays.ts +10 -0
  448. package/src/utils/blob.ts +1 -1
  449. package/src/utils/dates.ts +94 -0
  450. package/src/utils/numbers.ts +79 -18
  451. package/src/utils/objects.ts +3 -7
  452. package/src/utils/once.ts +19 -19
  453. package/src/utils/tracer.ts +9 -5
  454. package/src/utils/zod-utils.ts +1 -3
  455. package/dist/any-language-editor-B9REGlD9.js +0 -76
  456. package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
  457. package/dist/assets/worker-HPY9fbhz.js +0 -114
  458. package/dist/error-entries-CzoYkIQZ.js +0 -56
  459. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
  460. package/dist/precisionRound-DMIVQrt2.js +0 -97
  461. package/src/core/state/__tests__/jotai.test.ts +0 -25
  462. package/src/core/wasm/worker/message-buffer.ts +0 -33
  463. package/src/core/websocket/__tests__/useWebSocket.test.ts +0 -49
  464. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  465. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -338
  466. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  467. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  468. package/src/core/websocket/transports/basic.ts +0 -102
  469. package/src/core/websocket/transports/sse.ts +0 -308
  470. package/src/core/websocket/transports/transport.ts +0 -61
  471. package/src/core/websocket/transports/ws.ts +0 -98
  472. package/src/core/websocket/useWebSocket.tsx +0 -88
@@ -1,7 +1,8 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  /* oxlint-disable typescript/no-explicit-any */
3
3
 
4
- import { useCallback, useEffect, useRef, useState } from "react";
4
+ import { useEffect, useRef, useState } from "react";
5
+ import useEvent from "react-use-event-hook";
5
6
  import { z } from "zod";
6
7
  import { MarimoIncomingMessageEvent } from "@/core/dom/events";
7
8
  import {
@@ -77,7 +78,7 @@ type T = Record<string, unknown>;
77
78
 
78
79
  // oxlint-disable-next-line typescript/consistent-type-definitions
79
80
  type PluginFunctions = {
80
- send_to_widget: <T>(req: {
81
+ send_to_widget: (req: {
81
82
  message?: unknown;
82
83
  buffers?: unknown;
83
84
  }) => Promise<null | undefined>;
@@ -146,17 +147,10 @@ const PanelSlot = (props: Props) => {
146
147
  > | null>(null);
147
148
  const [loaded, setLoaded] = useState<boolean>(false);
148
149
  const [rendered, setRendered] = useState<string | null>(null);
150
+ const eventBufferRef = useRef<EventBuffer<unknown> | null>(null);
149
151
 
150
- // Store functions in a ref so the callback captured by EventBuffer stays current
151
- const functionsRef = useRef(functions);
152
- functionsRef.current = functions;
153
-
154
- const processEvents = useCallback(() => {
155
- if (!eventBufferRef.current) {
156
- return;
157
- }
158
-
159
- const events = eventBufferRef.current.getAndClear();
152
+ const processEvents = useEvent(() => {
153
+ const events = eventBufferRef.current?.getAndClear() ?? [];
160
154
  if (events.length === 0) {
161
155
  return;
162
156
  }
@@ -182,14 +176,19 @@ const PanelSlot = (props: Props) => {
182
176
  );
183
177
  const buffers: ArrayBuffer[] = [];
184
178
  message.content = extractBuffers(message.content, buffers);
185
- functionsRef.current.send_to_widget({ message, buffers }).catch((error) => {
179
+ functions.send_to_widget({ message, buffers }).catch((error) => {
186
180
  Logger.warn("Failed to send Panel event to backend", error);
187
181
  });
188
- }, []);
182
+ });
189
183
 
190
- const eventBufferRef = useRef<EventBuffer<unknown> | null>(
191
- new EventBuffer(processEvents),
192
- );
184
+ useEffect(() => {
185
+ const buffer = new EventBuffer(processEvents);
186
+ eventBufferRef.current = buffer;
187
+ return () => {
188
+ buffer.dispose();
189
+ eventBufferRef.current = null;
190
+ };
191
+ }, [processEvents]);
193
192
 
194
193
  // Load the bokeh extension
195
194
  useEffect(() => {
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import { EventBuffer, extractBuffers, MessageSchema } from "../utils";
4
4
 
5
5
  describe("MessageSchema", () => {
@@ -73,6 +73,44 @@ describe("EventBuffer", () => {
73
73
  eventBuffer = new EventBuffer(processEvents, 50);
74
74
  });
75
75
 
76
+ afterEach(() => {
77
+ eventBuffer.dispose();
78
+ vi.useRealTimers();
79
+ });
80
+
81
+ it("flushes queued events and cancels the pending timer on dispose", () => {
82
+ eventBuffer.add("event1");
83
+ eventBuffer.add("event2");
84
+ expect(vi.getTimerCount()).toBe(1);
85
+ expect(processEvents).toHaveBeenCalledOnce();
86
+
87
+ eventBuffer.dispose();
88
+ expect(processEvents).toHaveBeenCalledTimes(2);
89
+ eventBuffer.dispose();
90
+
91
+ expect(eventBuffer.size()).toBe(0);
92
+ expect(vi.getTimerCount()).toBe(0);
93
+ vi.advanceTimersByTime(100);
94
+ expect(processEvents).toHaveBeenCalledTimes(2);
95
+ });
96
+
97
+ it("does not process an empty buffer on dispose", () => {
98
+ eventBuffer.dispose();
99
+ expect(processEvents).not.toHaveBeenCalled();
100
+ });
101
+
102
+ it("resets the blocked state when disposed", () => {
103
+ eventBuffer.add("event1");
104
+ // Emulate a real processEvents, which drains the buffer
105
+ eventBuffer.getAndClear();
106
+ eventBuffer.dispose();
107
+ eventBuffer.add("event2");
108
+
109
+ expect(processEvents).toHaveBeenCalledTimes(2);
110
+ expect(eventBuffer.getAndClear()).toEqual(["event2"]);
111
+ expect(vi.getTimerCount()).toBe(1);
112
+ });
113
+
76
114
  it("should add events to buffer", () => {
77
115
  eventBuffer.add("event1");
78
116
  eventBuffer.add("event2");
@@ -83,6 +83,7 @@ export class EventBuffer<T> {
83
83
  clearTimeout(this.timeout);
84
84
  }
85
85
  this.timeout = window.setTimeout(() => {
86
+ this.timeout = null;
86
87
  this.isBlocked = false;
87
88
  if (this.buffer.length > 0) {
88
89
  this.flush();
@@ -98,6 +99,22 @@ export class EventBuffer<T> {
98
99
  this.buffer = [];
99
100
  }
100
101
 
102
+ /**
103
+ * Cancel the pending timer, flushing any events still held by the block
104
+ * window so the last user interaction is not lost.
105
+ */
106
+ public dispose() {
107
+ if (this.timeout !== null) {
108
+ clearTimeout(this.timeout);
109
+ this.timeout = null;
110
+ }
111
+ this.isBlocked = false;
112
+ if (this.buffer.length > 0) {
113
+ this.processEvents();
114
+ }
115
+ this.clear();
116
+ }
117
+
101
118
  public getAndClear() {
102
119
  const events = this.buffer;
103
120
  this.buffer = [];
@@ -58,11 +58,8 @@ export class PlotlyPlugin implements IPlugin<T, Data> {
58
58
  public tagName = "marimo-plotly";
59
59
 
60
60
  public validator = z.object({
61
- figure: z
62
- .object({})
63
- .passthrough()
64
- .transform((spec) => spec as unknown as Figure),
65
- config: z.object({}).passthrough(),
61
+ figure: z.looseObject({}).transform((spec) => spec as unknown as Figure),
62
+ config: z.looseObject({}),
66
63
  });
67
64
 
68
65
  public render(props: IPluginProps<T, Data>): JSX.Element {
@@ -208,13 +208,19 @@ export function hasAreaTrace(
208
208
  });
209
209
  }
210
210
 
211
- function createDragmodeButton(
212
- name: string,
213
- title: string,
214
- svg: string,
215
- dragmode: Plotly.Layout["dragmode"],
216
- setDragmode: (dragmode: Plotly.Layout["dragmode"]) => void,
217
- ): ModeBarButton {
211
+ function createDragmodeButton({
212
+ name,
213
+ title,
214
+ svg,
215
+ dragmode,
216
+ setDragmode,
217
+ }: {
218
+ name: string;
219
+ title: string;
220
+ svg: string;
221
+ dragmode: Plotly.Layout["dragmode"];
222
+ setDragmode: (dragmode: Plotly.Layout["dragmode"]) => void;
223
+ }): ModeBarButton {
218
224
  return {
219
225
  name,
220
226
  title,
@@ -227,27 +233,27 @@ export function lineSelectionButtons(
227
233
  setDragmode: (dragmode: Plotly.Layout["dragmode"]) => void,
228
234
  ): ModeBarButton[] {
229
235
  return [
230
- createDragmodeButton(
231
- "line-box-select",
232
- "Box select",
233
- `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
236
+ createDragmodeButton({
237
+ name: "line-box-select",
238
+ title: "Box select",
239
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
234
240
  stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
235
241
  <rect x="4" y="4" width="16" height="16" stroke-dasharray="2 2" />
236
242
  </svg>`,
237
- "select",
243
+ dragmode: "select",
238
244
  setDragmode,
239
- ),
240
- createDragmodeButton(
241
- "line-lasso-select",
242
- "Lasso select",
243
- `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
245
+ }),
246
+ createDragmodeButton({
247
+ name: "line-lasso-select",
248
+ title: "Lasso select",
249
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
244
250
  stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
245
251
  <path d="M6 8c0-2.2 2.2-4 5-4s5 1.8 5 4-2.2 4-5 4-5 1.8-5 4 2.2 4 5 4" />
246
252
  <circle cx="17.5" cy="16.5" r="1.5" />
247
253
  </svg>`,
248
- "lasso",
254
+ dragmode: "lasso",
249
255
  setDragmode,
250
- ),
256
+ }),
251
257
  ];
252
258
  }
253
259
 
@@ -16,14 +16,13 @@ export class VegaPlugin implements IPlugin<VegaComponentState, Data> {
16
16
 
17
17
  public validator = z.object({
18
18
  spec: z
19
- .object({})
20
- .passthrough()
19
+ .looseObject({})
21
20
  .transform((spec) => spec as unknown as VegaLiteSpec),
22
21
  chartSelection: z
23
22
  .union([z.boolean(), z.literal("point"), z.literal("interval")])
24
23
  .default(true),
25
24
  fieldSelection: z.union([z.boolean(), z.array(z.string())]).default(true),
26
- embedOptions: z.object({}).passthrough().default({}),
25
+ embedOptions: z.looseObject({}).default({}),
27
26
  });
28
27
 
29
28
  public render(props: IPluginProps<VegaComponentState, Data>): JSX.Element {
@@ -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";
@@ -218,7 +219,7 @@ function removeSpecificParamsFromNestedSpecs(
218
219
  }
219
220
 
220
221
  if (filteredParams.length === 0) {
221
- const { params, ...rest } = spec;
222
+ const { params: _params, ...rest } = spec;
222
223
  return rest as SpecWithParams;
223
224
  }
224
225
 
@@ -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 = legendFields.filter((field) =>
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 = legendFields.filter((field) =>
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));
@@ -56,7 +56,7 @@ const RoutesComponent = ({
56
56
 
57
57
  useEffect(() => {
58
58
  // Listen for route changes
59
- const listener = (e: PopStateEvent | HashChangeEvent) => {
59
+ const listener = () => {
60
60
  handleFindMatch(window.location);
61
61
  };
62
62
  window.addEventListener("hashchange", listener);
@@ -0,0 +1,33 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { EditorView } from "@codemirror/view";
3
+ import { composeStories } from "@storybook/react-vite";
4
+ import { act, render } from "@testing-library/react";
5
+ import { expect, it, vi } from "vitest";
6
+ import * as stories from "../editor.stories";
7
+
8
+ vi.mock("@/core/codemirror/cm", () => ({
9
+ basicBundle: () => [],
10
+ }));
11
+
12
+ const { Primary } = composeStories(stories);
13
+
14
+ it("preserves editor changes when the story rerenders with the same theme", () => {
15
+ const { container, rerender } = render(<Primary />);
16
+ const editor = container.querySelector<HTMLElement>(".cm-editor");
17
+ expect(editor).not.toBeNull();
18
+ const view = editor && EditorView.findFromDOM(editor);
19
+ expect(view).toBeDefined();
20
+ if (!view) {
21
+ throw new Error("Editor view was not mounted");
22
+ }
23
+
24
+ act(() => {
25
+ view.dispatch({
26
+ changes: { from: 0, to: view.state.doc.length, insert: "user edit" },
27
+ });
28
+ });
29
+ rerender(<Primary />);
30
+
31
+ expect(container.querySelector(".cm-editor")).toBe(editor);
32
+ expect(view.state.doc.toString()).toBe("user edit");
33
+ });
@@ -78,6 +78,7 @@ export const Loading = {
78
78
  export const TrashIcon = {
79
79
  render: () => (
80
80
  <Button
81
+ aria-label="Delete"
81
82
  variant="ghost"
82
83
  size="icon"
83
84
  className="hover:bg-transparent hover:border-input"
@@ -5,7 +5,7 @@ import { EditorState, type Extension } from "@codemirror/state";
5
5
  import { EditorView } from "@codemirror/view";
6
6
  import type { Meta, StoryObj } from "@storybook/react-vite";
7
7
  import CodeMirror from "@uiw/react-codemirror";
8
- import { useEffect, useRef } from "react";
8
+ import { useEffect, useMemo, useRef } from "react";
9
9
  import { CopilotConfig } from "@/core/codemirror/copilot/copilot-config";
10
10
  import { copilotBundle } from "@/core/codemirror/copilot/extension";
11
11
  import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
@@ -52,28 +52,35 @@ const Editor = (opts: { extensions?: Extension[] }): React.ReactNode => {
52
52
  });
53
53
 
54
54
  return () => view.destroy();
55
- // oxlint-disable-next-line react-hooks/exhaustive-deps
56
- }, [ref.current]);
55
+ }, [opts.extensions]);
57
56
 
58
57
  return <div className="cm" ref={ref} />;
59
58
  };
60
59
 
61
- export const Primary: Story = {
62
- render: (_args, ctx) => (
60
+ const PrimaryEditor = ({ theme }: Pick<CodeMirrorSetupOpts, "theme">) => {
61
+ const extensions = useMemo(
62
+ () =>
63
+ basicBundle({
64
+ completionConfig: {
65
+ activate_on_typing: false,
66
+ copilot: false,
67
+ signature_hint_on_typing: false,
68
+ },
69
+ theme,
70
+ hotkeys: new OverridingHotkeyProvider({}),
71
+ } as unknown as CodeMirrorSetupOpts),
72
+ [theme],
73
+ );
74
+
75
+ return (
63
76
  <div className="Cell m-20 w-[60%] overflow-hidden">
64
- <Editor
65
- extensions={basicBundle({
66
- completionConfig: {
67
- activate_on_typing: false,
68
- copilot: false,
69
- signature_hint_on_typing: false,
70
- },
71
- theme: ctx.globals.theme,
72
- hotkeys: new OverridingHotkeyProvider({}),
73
- } as unknown as CodeMirrorSetupOpts)}
74
- />
77
+ <Editor extensions={extensions} />
75
78
  </div>
76
- ),
79
+ );
80
+ };
81
+
82
+ export const Primary: Story = {
83
+ render: (_args, ctx) => <PrimaryEditor theme={ctx.globals.theme} />,
77
84
  };
78
85
 
79
86
  export const DefaultPython: Story = {
@@ -20,7 +20,7 @@ type Story = StoryObj<typeof FormWrapper>;
20
20
  const props: FormWrapperProps<string> = {
21
21
  currentValue: "currentValue",
22
22
  newValue: "currentValue",
23
- setValue: (v) => {
23
+ setValue: () => {
24
24
  toast({
25
25
  title: "Form submitted",
26
26
  });
@@ -133,7 +133,7 @@ export const Validate: Story = {
133
133
  <FormWrapper
134
134
  {...props}
135
135
  shouldValidate={true}
136
- validate={async ({ value }) => {
136
+ validate={async () => {
137
137
  const random = Math.random();
138
138
  if (random < 0.8) {
139
139
  return `Failed to validate. Random number was ${random}`;
@@ -24,7 +24,7 @@ export default meta;
24
24
  type Story = StoryObj;
25
25
 
26
26
  export const Primary: Story = {
27
- render: (args, ctx) => (
27
+ render: () => (
28
28
  <Select>
29
29
  <SelectTrigger className="w-64">
30
30
  <SelectValue placeholder="Select a fruit" />
@@ -63,7 +63,7 @@ export const Primary: Story = {
63
63
  };
64
64
 
65
65
  export const Clearable: Story = {
66
- render: (args, ctx) => {
66
+ render: () => {
67
67
  const [value, setValue] = useState<string>("");
68
68
  return (
69
69
  <>
@@ -15,7 +15,7 @@ export default meta;
15
15
  type Story = StoryObj;
16
16
 
17
17
  export const Primary: Story = {
18
- render: (args, ctx) => {
18
+ render: () => {
19
19
  const [value, setValue] = useState<string[]>([]);
20
20
  return (
21
21
  <TooltipProvider>
@@ -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]);
@@ -8,7 +8,7 @@ async function sleep(ms: number) {
8
8
 
9
9
  describe("batch", () => {
10
10
  it("should return the same promise for calls with the same key", async () => {
11
- const loader = vi.fn().mockImplementation(async (arg) => {
11
+ const loader = vi.fn().mockImplementation(async () => {
12
12
  await sleep(10);
13
13
  return "response";
14
14
  });
@@ -25,7 +25,7 @@ describe("batch", () => {
25
25
  });
26
26
 
27
27
  it("should call loader again after the first promise resolves", async () => {
28
- const loader = vi.fn().mockImplementation(async (arg) => {
28
+ const loader = vi.fn().mockImplementation(async () => {
29
29
  await sleep(10);
30
30
  return "response";
31
31
  });
@@ -40,7 +40,7 @@ describe("batch", () => {
40
40
  });
41
41
 
42
42
  it("should handle different keys separately", async () => {
43
- const loader = vi.fn().mockImplementation(async (arg) => {
43
+ const loader = vi.fn().mockImplementation(async () => {
44
44
  await sleep(10);
45
45
  return "response";
46
46
  });
@@ -4,6 +4,9 @@ import {
4
4
  exactDateTime,
5
5
  getDateFormat,
6
6
  getShortTimeZone,
7
+ parseLocalISODate,
8
+ parseLocalISODateTime,
9
+ parseTimeToDate,
7
10
  prettyDate,
8
11
  timeAgo,
9
12
  } from "../dates";
@@ -191,6 +194,119 @@ describe("dates", () => {
191
194
  });
192
195
  });
193
196
 
197
+ describe("parseTimeToDate", () => {
198
+ it("parses 24-hour times", () => {
199
+ expect(parseTimeToDate("14:30")).toEqual(new Date(1970, 0, 1, 14, 30));
200
+ expect(parseTimeToDate("14:30:05")).toEqual(
201
+ new Date(1970, 0, 1, 14, 30, 5),
202
+ );
203
+ });
204
+
205
+ it("parses times with an am/pm suffix", () => {
206
+ expect(parseTimeToDate("2:30 PM")).toEqual(new Date(1970, 0, 1, 14, 30));
207
+ expect(parseTimeToDate("12:00 am")).toEqual(new Date(1970, 0, 1, 0, 0));
208
+ expect(parseTimeToDate("12:00 pm")).toEqual(new Date(1970, 0, 1, 12, 0));
209
+ });
210
+
211
+ it("rejects an out-of-range hour instead of letting Date normalize it", () => {
212
+ // new Date(1970, 0, 1, 25, 0) would silently become 01:00 the next day.
213
+ expect(parseTimeToDate("25:00")).toBeUndefined();
214
+ });
215
+
216
+ it("rejects an out-of-range minute instead of letting Date normalize it", () => {
217
+ // new Date(1970, 0, 1, 12, 99) would silently become 13:39.
218
+ expect(parseTimeToDate("12:99")).toBeUndefined();
219
+ });
220
+
221
+ it("rejects an out-of-range second", () => {
222
+ expect(parseTimeToDate("12:30:99")).toBeUndefined();
223
+ });
224
+
225
+ it("requires an hour from 1 to 12 with an am/pm suffix", () => {
226
+ expect(parseTimeToDate("0:30 am")).toBeUndefined();
227
+ expect(parseTimeToDate("13:30 pm")).toBeUndefined();
228
+ });
229
+
230
+ it("parses milliseconds", () => {
231
+ expect(parseTimeToDate("14:30:05.123")).toEqual(
232
+ new Date(1970, 0, 1, 14, 30, 5, 123),
233
+ );
234
+ });
235
+
236
+ it("pads a short millisecond fraction and truncates a long one", () => {
237
+ expect(parseTimeToDate("14:30:05.5")).toEqual(
238
+ new Date(1970, 0, 1, 14, 30, 5, 500),
239
+ );
240
+ expect(parseTimeToDate("14:30:05.123456")).toEqual(
241
+ new Date(1970, 0, 1, 14, 30, 5, 123),
242
+ );
243
+ });
244
+ });
245
+
246
+ describe("parseLocalISODate", () => {
247
+ it("parses a date-only string into local midnight", () => {
248
+ expect(parseLocalISODate("2024-01-15")).toEqual(new Date(2024, 0, 15));
249
+ });
250
+
251
+ it("returns undefined for a non-date-only string", () => {
252
+ expect(parseLocalISODate("2024-01-15T00:00:00")).toBeUndefined();
253
+ expect(parseLocalISODate("")).toBeUndefined();
254
+ expect(parseLocalISODate("not-a-date")).toBeUndefined();
255
+ });
256
+
257
+ it("returns undefined for a calendar date that doesn't exist", () => {
258
+ expect(parseLocalISODate("2024-02-30")).toBeUndefined();
259
+ expect(parseLocalISODate("2024-13-01")).toBeUndefined();
260
+ });
261
+ });
262
+
263
+ describe("parseLocalISODateTime", () => {
264
+ it("parses a 'T'-separated datetime into local wall-clock time", () => {
265
+ expect(parseLocalISODateTime("2024-01-15T08:30:00")).toEqual(
266
+ new Date(2024, 0, 15, 8, 30, 0),
267
+ );
268
+ });
269
+
270
+ it("parses a space-separated datetime", () => {
271
+ expect(parseLocalISODateTime("2024-01-15 08:30:00")).toEqual(
272
+ new Date(2024, 0, 15, 8, 30, 0),
273
+ );
274
+ });
275
+
276
+ it("defaults seconds to 0 when omitted", () => {
277
+ expect(parseLocalISODateTime("2024-01-15T08:30")).toEqual(
278
+ new Date(2024, 0, 15, 8, 30, 0),
279
+ );
280
+ });
281
+
282
+ it("ignores a trailing Z instead of shifting to UTC", () => {
283
+ expect(parseLocalISODateTime("2024-01-15T08:30:00Z")).toEqual(
284
+ new Date(2024, 0, 15, 8, 30, 0),
285
+ );
286
+ });
287
+
288
+ it("ignores a trailing UTC offset instead of shifting the time", () => {
289
+ expect(parseLocalISODateTime("2024-01-15T08:30:00+05:00")).toEqual(
290
+ new Date(2024, 0, 15, 8, 30, 0),
291
+ );
292
+ expect(parseLocalISODateTime("2024-01-15T08:30:00-0500")).toEqual(
293
+ new Date(2024, 0, 15, 8, 30, 0),
294
+ );
295
+ });
296
+
297
+ it("ignores sub-second precision", () => {
298
+ expect(parseLocalISODateTime("2024-01-15T08:30:00.123")).toEqual(
299
+ new Date(2024, 0, 15, 8, 30, 0),
300
+ );
301
+ });
302
+
303
+ it("returns undefined for a date-only or unparsable string", () => {
304
+ expect(parseLocalISODateTime("2024-01-15")).toBeUndefined();
305
+ expect(parseLocalISODateTime("not-a-datetime")).toBeUndefined();
306
+ expect(parseLocalISODateTime("")).toBeUndefined();
307
+ });
308
+ });
309
+
194
310
  describe("getDateFormat", () => {
195
311
  it("returns the correct format for date", () => {
196
312
  expect(getDateFormat("2023-05-15")).toBe("yyyy-MM-dd");