@marimo-team/islands 0.25.2-dev99 → 0.25.3-dev10

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 (205) hide show
  1. package/dist/{ConnectedDataExplorerComponent-BZmqc_F0.js → ConnectedDataExplorerComponent-CNMliVzw.js} +15 -15
  2. package/dist/{ErrorBoundary-CXkr_IcB.js → ErrorBoundary-BsqSMK9Y.js} +12 -12
  3. package/dist/{ImageComparisonComponent-Cpfz30OZ.js → ImageComparisonComponent-uU8FSKZE.js} +3 -3
  4. package/dist/{Plot-CTyjbQDT.js → Plot-DdYOe1UK.js} +2 -2
  5. package/dist/{abnfDiagram-VCTEODGH-C_Lmaa2j.js → abnfDiagram-VCTEODGH-D-Ez91gV.js} +3 -3
  6. package/dist/{any-language-editor-BFzqhOBR.js → any-language-editor-DZ_YTVus.js} +7 -7
  7. package/dist/{architectureDiagram-5GKGNRK7-BKMataHB.js → architectureDiagram-5GKGNRK7-CTpcNtO8.js} +5 -5
  8. package/dist/assets/__vite-browser-external-BXYkiFsk.js +1 -0
  9. package/dist/assets/worker-HPY9fbhz.js +114 -0
  10. package/dist/{blockDiagram-I7D4REHJ-BeHOjyDk.js → blockDiagram-I7D4REHJ-Cg25UrxT.js} +9 -9
  11. package/dist/{button-BtsM2UqM.js → button-CvjkwdSk.js} +5 -5
  12. package/dist/{c4Diagram-7LVT6UL2-Bao10t_O.js → c4Diagram-7LVT6UL2-BxFgrnNV.js} +7 -7
  13. package/dist/{capabilities-A5jrzjCJ.js → capabilities-CQ5IjhzL.js} +1 -1
  14. package/dist/{channel-BxCkvUgE.js → channel-CPObtJXj.js} +1 -1
  15. package/dist/{chat-ui-CwOWzoSO.js → chat-ui-0lKdOoQ3.js} +1588 -1396
  16. package/dist/{check-CNP79M3A.js → check-EDwSwP89.js} +1 -1
  17. package/dist/{chunk-4HAMMTFA-CH7Gpamd.js → chunk-4HAMMTFA-D4eFPyFh.js} +5 -5
  18. package/dist/{chunk-75Z2AOVW-AI7CvgKn.js → chunk-75Z2AOVW-D7gxIs2Q.js} +1 -1
  19. package/dist/{chunk-DU6HZSFF-BSvuTkX4.js → chunk-DU6HZSFF-BwiyaNpg.js} +1 -1
  20. package/dist/{chunk-F27PBJKO-CLshwT9T.js → chunk-F27PBJKO-ufseIn5y.js} +1 -1
  21. package/dist/{chunk-GMAD6QVW-wnHcn-lH.js → chunk-GMAD6QVW-BhaJx0X3.js} +3 -3
  22. package/dist/{chunk-GVQU2GXP-7O2p8hut.js → chunk-GVQU2GXP-Cwbt-lF0.js} +2 -2
  23. package/dist/{chunk-IMKFNOWR-Bu0Vh_Yi.js → chunk-IMKFNOWR-DGxi-zeo.js} +5 -5
  24. package/dist/{chunk-L3NEJ4N5-B9_Alrxb.js → chunk-L3NEJ4N5-n-zgIVSy.js} +5 -5
  25. package/dist/{chunk-OSK3NFVY-PeQPH2fD.js → chunk-OSK3NFVY-DXa-Cxbn.js} +6 -6
  26. package/dist/{chunk-P2QGCYS3-D9twiFuU.js → chunk-P2QGCYS3-B_Zd3-X0.js} +1 -1
  27. package/dist/{chunk-POPQ4Y6H-BUiNs18s.js → chunk-POPQ4Y6H-hYOXbYH_.js} +1 -1
  28. package/dist/{chunk-PWAF6VOD-D8S9NPCy.js → chunk-PWAF6VOD-CQw5qATZ.js} +1 -1
  29. package/dist/{chunk-SHT3W25Y-B2dyNZ31.js → chunk-SHT3W25Y-CV9J3rAB.js} +7 -7
  30. package/dist/{chunk-SVP7TREG-D7UPvB10.js → chunk-SVP7TREG-BL-9Fqgg.js} +2 -2
  31. package/dist/{chunk-TICWLB2K-BNaGoadd.js → chunk-TICWLB2K-Cu_XMjDM.js} +5 -5
  32. package/dist/{chunk-YOKDWASO-DrWbKnxA.js → chunk-YOKDWASO-DPZk8vhS.js} +6 -6
  33. package/dist/classDiagram-ZZMXUADV-BXXV13Hq.js +30 -0
  34. package/dist/classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js +30 -0
  35. package/dist/{code-visibility-pPjYsRTH.js → code-visibility-CXQvUXHT.js} +18 -18
  36. package/dist/{common-CkvBp0B4.js → common-CuD-CAC-.js} +6128 -7136
  37. package/dist/{compiler-runtime-Cj8ZWiNS.js → compiler-runtime-CldRdSsv.js} +2 -2
  38. package/dist/{copy-icon-BY7jgcaV.js → copy-icon-Ft7t9bqz.js} +6 -6
  39. package/dist/{cynefinDiagram-5FMLGOSQ-CCDn4Z9e.js → cynefinDiagram-5FMLGOSQ-CH96rwba.js} +3 -3
  40. package/dist/{dagre-GXQ25YYZ-BJiUO_SV.js → dagre-GXQ25YYZ-BD3PmpFY.js} +10 -10
  41. package/dist/{data-grid-overlay-editor-L6ELxFAh.js → data-grid-overlay-editor-DgeyYmC1.js} +7 -7
  42. package/dist/{data-grid-types-BucVmDDp.js → data-grid-types-BH_r7g7l.js} +1 -1
  43. package/dist/{diagram-S7CK7UJ4-BZfxFNRR.js → diagram-S7CK7UJ4-BsSJJldB.js} +4 -4
  44. package/dist/{diagram-UQ7AKVKN-1xcsiIMg.js → diagram-UQ7AKVKN-muQtNZc4.js} +3 -3
  45. package/dist/{diagram-VSXAHHWV-DPAWJ9Nw.js → diagram-VSXAHHWV-BJWcVVJY.js} +2 -2
  46. package/dist/{diagram-VX7I27RA-DmRsnHYY.js → diagram-VX7I27RA-CdhuZAqR.js} +5 -5
  47. package/dist/{diagram-Z3DM3KII--b49ryKx.js → diagram-Z3DM3KII-BD8mXjOj.js} +3 -3
  48. package/dist/{dialog-CmExhUDI.js → dialog-CEBFZNyc.js} +6 -6
  49. package/dist/{dist-CcyKTBTF.js → dist-DpBpA_n1.js} +2 -2
  50. package/dist/{dropdown-menu-Cz4TdOJh.js → dropdown-menu-CP_qCYHC.js} +139 -139
  51. package/dist/{ebnfDiagram-PWID7BFC-rSwVyh4y.js → ebnfDiagram-PWID7BFC-BIN1sweE.js} +3 -3
  52. package/dist/{erDiagram-RLTQ6QDP-Bfd3iAoC.js → erDiagram-RLTQ6QDP-Ctp5cfoN.js} +12 -12
  53. package/dist/{error-banner-D30nA9nE.js → error-banner-D-WCIiMt.js} +16 -11
  54. package/dist/{cells-DZi0ioak.js → error-entries-DSvFWT_M.js} +6171 -5075
  55. package/dist/{esm-DAVrGVKP.js → esm-AMFssoNp.js} +4 -4
  56. package/dist/{feedback-button-D-pV0Qji.js → feedback-button-DZMj9PF3.js} +119 -120
  57. package/dist/flowDiagram-HODETNUW-3EfLrlFb.js +16 -0
  58. package/dist/{formats-DhhbdyAH.js → formats-CeOmU1MU.js} +6 -6
  59. package/dist/{ganttDiagram-EL5Y4UJY-B4m_YsKa.js → ganttDiagram-EL5Y4UJY-BQT16JSc.js} +2 -2
  60. package/dist/{gitGraphDiagram-WWUBYQGX-QRBbvWXh.js → gitGraphDiagram-WWUBYQGX-D5nSIzdE.js} +2 -2
  61. package/dist/{glide-data-editor-D6CfYwdr.js → glide-data-editor-g_L8FLAC.js} +60 -60
  62. package/dist/{glide-utils-BVJ4fkxX.js → glide-utils-DJyqh0EM.js} +4 -4
  63. package/dist/{globals-BwK9YGrX.js → globals-CYGlmBXb.js} +3 -3
  64. package/dist/{highlighted-body-KPVGNVTW-Ad3MKZ18.js → highlighted-body-KPVGNVTW-zwjq21tf.js} +4 -4
  65. package/dist/{html-to-image-Cn1_16-x.js → html-to-image-B8s5SgGm.js} +37 -37
  66. package/dist/{infoDiagram-27XIBGKW-DVQFYvhg.js → infoDiagram-27XIBGKW-DipQoHxl.js} +2 -2
  67. package/dist/{ishikawaDiagram-5VMMS53U-viPND-nm.js → ishikawaDiagram-5VMMS53U-DgFMjSGz.js} +3 -3
  68. package/dist/{journeyDiagram-3NMN7TZE-BeeJdJo3.js → journeyDiagram-3NMN7TZE-BaLqn-dZ.js} +2 -2
  69. package/dist/{jsx-runtime-DgDPJV9S.js → jsx-runtime-3BD1kT0s.js} +1 -1
  70. package/dist/{kanban-definition-UXKFOSKX-Dcl_ECO9.js → kanban-definition-UXKFOSKX-w1ACVjgl.js} +9 -9
  71. package/dist/{katex-BNCmKUol.js → katex-BIdZGsyO.js} +141 -116
  72. package/dist/{label-DQO1jFjf.js → label-BDKF3Afc.js} +10 -10
  73. package/dist/{loader-Cm-ATtou.js → loader-BQOfjTIJ.js} +1 -1
  74. package/dist/main.js +623 -590
  75. package/dist/{mermaid-BfhcnSj-.js → mermaid-CHo49Yv-.js} +56 -56
  76. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +2 -0
  77. package/dist/{mhchem-BEc7A1or.js → mhchem-CdWPJK6g.js} +1 -1
  78. package/dist/{mindmap-definition-YA3MSWOX-BdxxhF-5.js → mindmap-definition-YA3MSWOX-CrT0KSfX.js} +11 -11
  79. package/dist/{number-overlay-editor-tMylyldi.js → number-overlay-editor-9VRv-pdj.js} +5 -5
  80. package/dist/{pegDiagram-XKGWAZYB-Du5p2s0w.js → pegDiagram-XKGWAZYB-BqvywNJA.js} +3 -3
  81. package/dist/{pieDiagram-E7YTZNPT-DJg-SOIq.js → pieDiagram-E7YTZNPT-BfxJ_XCc.js} +3 -3
  82. package/dist/{process-output-CUG57KRp.js → process-output-CKRXyszb.js} +5 -5
  83. package/dist/{quadrantDiagram-AXDQQJYC-keLfFx9f.js → quadrantDiagram-AXDQQJYC-Ccw0xMjN.js} +1 -1
  84. package/dist/{railroadDiagram-O6MQD6OU-C0ojm-6_.js → railroadDiagram-O6MQD6OU-8vFLVTPu.js} +3 -3
  85. package/dist/{react-dom-BUgJ-ab_.js → react-dom-DUmH0GXv.js} +29 -21
  86. package/dist/{react-ofJbxM4x.js → react-sHtZdjeP.js} +103 -94
  87. package/dist/{react-vega-ewj7q_U5.js → react-vega-BED0DjiB.js} +3 -3
  88. package/dist/{requirementDiagram-BXWQKSXE-CNwNBmI3.js → requirementDiagram-BXWQKSXE-a_MxGvxe.js} +11 -11
  89. package/dist/{reveal-component-LXUMcqWX.js → reveal-component-C87mv9Mn.js} +906 -878
  90. package/dist/{sankeyDiagram-P5KCCOFB-Bh4EsJox.js → sankeyDiagram-P5KCCOFB-ELvOcnZH.js} +1 -1
  91. package/dist/{select-Bs-nmvm6.js → select-BButFi-d.js} +11 -11
  92. package/dist/{sequenceDiagram-WJ2MYXX4-DQaW5qBG.js → sequenceDiagram-WJ2MYXX4-CXFMnxGh.js} +4 -4
  93. package/dist/{spec-DVsoMvYT.js → spec-BwarBpUJ.js} +4 -4
  94. package/dist/{stateDiagram-D77RDMKH-BDa515QC.js → stateDiagram-D77RDMKH-DQeA_CwI.js} +13 -13
  95. package/dist/stateDiagram-v2-MP3YSRHH-BxxQucm1.js +30 -0
  96. package/dist/style.css +1 -1
  97. package/dist/{swimlanes-42K2YHIH-M4IdGi8J.js → swimlanes-42K2YHIH-Do6L-cYg.js} +10 -10
  98. package/dist/swimlanesDiagram-VR7AAH4N-DUzPvSVU.js +30 -0
  99. package/dist/{swiper-component-CpN-hewZ.js → swiper-component-DKntyn8p.js} +5 -5
  100. package/dist/{timeline-definition-24CTP7MA-DH-kvakg.js → timeline-definition-24CTP7MA-D3c46WM5.js} +3 -3
  101. package/dist/{toDate-vEi5kTfY.js → toDate-vIXljnsr.js} +43 -42
  102. package/dist/{toast-DfEpJedt.js → toast-B9UMEi8u.js} +9 -9
  103. package/dist/{tooltip-CuNWg2ay.js → tooltip-BO1REbqs.js} +7 -7
  104. package/dist/{use-restore-focus-kmgwrL8q.js → use-restore-focus-BEM7sFWl.js} +2042 -1827
  105. package/dist/{use-vega-container-remeasure-4DwJbPWu.js → use-vega-container-remeasure-Bz4PZMtt.js} +2 -2
  106. package/dist/{useAsyncData-rkAlR_vh.js → useAsyncData-Deh5kszT.js} +3 -3
  107. package/dist/{useDateFormatter-C-tz65rD.js → useDateFormatter-D8XruNGG.js} +4 -4
  108. package/dist/{useDeepCompareMemoize-BlHFBj5_.js → useDeepCompareMemoize-DXwzRINI.js} +2 -2
  109. package/dist/{useIframeCapabilities-C1Bt04Iz.js → useIframeCapabilities-DBETvvPl.js} +2 -2
  110. package/dist/{useLifecycle-B68iY077.js → useLifecycle-j7D9uD1o.js} +6 -6
  111. package/dist/{useTheme-BwSgeAFb.js → useTheme-CRnjkku0.js} +7 -7
  112. package/dist/{vega-component-C-bPttRN.js → vega-component-DTG6zxaY.js} +14 -14
  113. package/dist/{vega-embed-container-sSR5hAsy.js → vega-embed-container-BNHE18G9.js} +5 -5
  114. package/dist/{vennDiagram-4TSXK5OY-d5g7XeiI.js → vennDiagram-4TSXK5OY-BSZD8JcZ.js} +3 -3
  115. package/dist/{wardleyDiagram-VM6X3IG4-BfX3bSzR.js → wardleyDiagram-VM6X3IG4-48cUWkfA.js} +3 -3
  116. package/dist/{xychartDiagram-S5SC5T6Z-C1QsLZg7.js → xychartDiagram-S5SC5T6Z-CswYgYeu.js} +5 -5
  117. package/package.json +25 -22
  118. package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
  119. package/src/__tests__/host-imports.test.ts +100 -0
  120. package/src/__tests__/mount.test.ts +8 -8
  121. package/src/components/data-table/__tests__/column_formatting.test.ts +30 -0
  122. package/src/components/data-table/__tests__/columns.test.tsx +70 -0
  123. package/src/components/data-table/__tests__/context-menu.test.ts +176 -0
  124. package/src/components/data-table/__tests__/date-filter-inputs.test.tsx +24 -1
  125. package/src/components/data-table/__tests__/export-actions.test.tsx +329 -6
  126. package/src/components/data-table/column-formatting/feature.ts +20 -1
  127. package/src/components/data-table/columns.tsx +25 -18
  128. package/src/components/data-table/context-menu.tsx +125 -26
  129. package/src/components/data-table/date-filter-inputs.tsx +15 -42
  130. package/src/components/data-table/export-actions.tsx +192 -116
  131. package/src/components/data-table/schemas.ts +14 -36
  132. package/src/components/data-table/utils.ts +18 -0
  133. package/src/components/editor/Output.tsx +9 -2
  134. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
  135. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +186 -34
  136. package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
  137. package/src/components/editor/actions/pair-with-agent-modal.tsx +96 -36
  138. package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
  139. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +372 -0
  140. package/src/components/editor/chrome/wrapper/footer-item.tsx +32 -24
  141. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +237 -0
  142. package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
  143. package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +4 -3
  144. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +548 -0
  145. package/src/components/editor/errors/send-error-report-button.tsx +69 -0
  146. package/src/components/editor/output/MarimoErrorOutput.tsx +21 -5
  147. package/src/components/editor/output/MarimoTracebackOutput.tsx +35 -20
  148. package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
  149. package/src/components/editor/output/ansi-reduce.ts +6 -0
  150. package/src/components/editor/output/console/ConsoleOutput.tsx +5 -3
  151. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +18 -0
  152. package/src/components/storage/__tests__/storage-file-viewer.test.tsx +2 -1
  153. package/src/core/attachments/__tests__/toast.test.tsx +58 -0
  154. package/src/core/attachments/state.ts +21 -0
  155. package/src/core/attachments/toast.ts +15 -0
  156. package/src/core/cells/__tests__/cells.test.ts +52 -0
  157. package/src/core/cells/__tests__/document-roundtrip.test.ts +16 -0
  158. package/src/core/cells/__tests__/error-report.test.ts +139 -0
  159. package/src/core/cells/__tests__/session.test.ts +50 -1
  160. package/src/core/cells/error-report.ts +63 -0
  161. package/src/core/cells/session.ts +28 -1
  162. package/src/core/codemirror/copilot/__tests__/completion-integration.test.ts +447 -0
  163. package/src/core/codemirror/copilot/__tests__/inline-completion.test.ts +395 -0
  164. package/src/core/codemirror/copilot/__tests__/language-server.test.ts +511 -2
  165. package/src/core/codemirror/copilot/__tests__/rpc-cancellation.test.ts +201 -0
  166. package/src/core/codemirror/copilot/extension.ts +22 -11
  167. package/src/core/codemirror/copilot/language-server.ts +109 -60
  168. package/src/core/codemirror/language/__tests__/sql.test.ts +4 -3
  169. package/src/core/codemirror/language/embedded/__tests__/embedded-python.test.ts +138 -26
  170. package/src/core/codemirror/language/embedded/embedded-python.ts +47 -13
  171. package/src/core/codemirror/language/embedded/latex.ts +20 -13
  172. package/src/core/config/pair.ts +5 -3
  173. package/src/core/datasets/__tests__/data-source-discovery.test.ts +18 -11
  174. package/src/core/host/AGENTS.md +6 -0
  175. package/src/core/host/__tests__/protocol.test.ts +214 -0
  176. package/src/core/host/protocol.ts +1943 -0
  177. package/src/core/islands/__tests__/bootstrap.test.ts +26 -0
  178. package/src/core/islands/bootstrap.ts +1 -0
  179. package/src/core/islands/bridge.ts +2 -2
  180. package/src/core/kernel/messages.ts +4 -7
  181. package/src/core/network/__tests__/api.test.ts +114 -1
  182. package/src/core/network/api.ts +3 -2
  183. package/src/core/wasm/bridge.ts +2 -2
  184. package/src/core/wasm/worker/types.ts +2 -2
  185. package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +193 -0
  186. package/src/core/websocket/useMarimoKernelConnection.tsx +9 -0
  187. package/src/css/katex-css.test.ts +1 -1
  188. package/src/css/katex.min.css +6 -1
  189. package/src/hooks/__tests__/useEventListener.test.tsx +45 -1
  190. package/src/hooks/useEventListener.ts +6 -1
  191. package/src/mount.tsx +3 -3
  192. package/src/plugins/impl/NumberPlugin.tsx +20 -3
  193. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +84 -0
  194. package/src/plugins/impl/__tests__/NumberPlugin.test.tsx +159 -1
  195. package/src/utils/__tests__/dates.test.ts +116 -0
  196. package/src/utils/dates.ts +94 -0
  197. package/dist/assets/__vite-browser-external-BqAtzd2P.js +0 -1
  198. package/dist/assets/worker-Bf8dt6S-.js +0 -109
  199. package/dist/classDiagram-ZZMXUADV-Bbo2n384.js +0 -30
  200. package/dist/classDiagram-v2-VYDZK3BY-xR8cGP0r.js +0 -30
  201. package/dist/error-entries-BovkFYNy.js +0 -56
  202. package/dist/flowDiagram-HODETNUW-D7IBKS12.js +0 -16
  203. package/dist/mermaid-HWGCJPDP-DYCzSILp.js +0 -2
  204. package/dist/stateDiagram-v2-MP3YSRHH-ChNddepZ.js +0 -30
  205. package/dist/swimlanesDiagram-VR7AAH4N-FUSwmzEp.js +0 -30
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 { pairPreviewAtom, pairPreviewSchema } from "@/core/config/pair";
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
- pairPreview: pairPreviewSchema.optional(),
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(pairPreviewAtom, parsedOptions.data.pairPreview);
350
+ store.set(pairConfigAtom, parsedOptions.data.pairConfig);
351
351
 
352
352
  // Runtime config
353
353
  if (parsedOptions.data.runtimeConfig.length > 0) {
@@ -4,6 +4,7 @@ import { z } from "zod";
4
4
  import { NumberField } from "@/components/ui/number-field";
5
5
  import { useDebounceControlledState } from "@/hooks/useDebounce";
6
6
  import { cn } from "@/utils/cn";
7
+ import { clamp } from "@/utils/math";
7
8
  import type { IPlugin, IPluginProps, Setter } from "../types";
8
9
  import { Labeled } from "./common/labeled";
9
10
 
@@ -65,8 +66,16 @@ const NumberComponent = (props: NumberComponentProps): JSX.Element => {
65
66
  onChange: props.setValue,
66
67
  });
67
68
 
69
+ const clampToRange = (n: number) =>
70
+ clamp(n, props.start ?? -Infinity, props.stop ?? Infinity);
71
+
68
72
  const handleChange = (newValue: number) => {
69
- onChange(withoutNaN(newValue));
73
+ const n = withoutNaN(newValue);
74
+ const next = n == null ? null : clampToRange(n);
75
+ // e.g. typing past `stop` while already at `stop`
76
+ if (next !== value) {
77
+ onChange(next);
78
+ }
70
79
  };
71
80
 
72
81
  return (
@@ -76,11 +85,19 @@ const NumberComponent = (props: NumberComponentProps): JSX.Element => {
76
85
  className={cn("min-w-[3em]", props.fullWidth && "w-full")}
77
86
  minValue={props.start ?? undefined}
78
87
  maxValue={props.stop ?? undefined}
79
- // This needs to be `?? NaN` since `?? undefined` makes uncontrolled component
88
+ // This needs to be `NaN` since `undefined` makes uncontrolled component
80
89
  // and can lead to leaving the old value in forms (https://github.com/marimo-team/marimo/issues/7352)
81
90
  // We out NaNs later
82
- value={value ?? Number.NaN}
91
+ value={value == null ? Number.NaN : clampToRange(value)}
83
92
  step={props.step}
93
+ // By default React Aria rounds every value to `start + n * step`, but
94
+ // `step` is only the stepper increment: users can enter any value in
95
+ // range (#9106). "validate" turns off that rounding (and the clamping,
96
+ // done above instead); its step validation is turned off so off-step
97
+ // values aren't flagged invalid or block `mo.ui.form` submission.
98
+ commitBehavior="validate"
99
+ validationBehavior="aria"
100
+ isInvalid={false}
84
101
  onChange={handleChange}
85
102
  id={id}
86
103
  aria-label={props.label || "Number input"}
@@ -136,6 +136,90 @@ describe("LoadingDataTableComponent", () => {
136
136
  expect(downloadGeoJSON).toHaveBeenCalledTimes(1);
137
137
  });
138
138
 
139
+ it("refreshes export metadata when a dataframe transform changes the data", async () => {
140
+ const host = document.createElement("div");
141
+ const metadataFor = (name: string) => ({
142
+ geometry_columns: [{ name, encoding: "objects", crs: "EPSG:4326" }],
143
+ primary_geometry_column: name,
144
+ default_geometry_column: name,
145
+ formats: {
146
+ parquet: { available: true, reason: null, missing_packages: [] },
147
+ },
148
+ });
149
+ const getExportMetadata = vi
150
+ .fn()
151
+ .mockResolvedValueOnce(metadataFor("before"))
152
+ .mockResolvedValueOnce(metadataFor("after"));
153
+ const view = (data: string) => (
154
+ <Provider store={store}>
155
+ <TooltipProvider>
156
+ <LoadingDataTableComponent
157
+ label={null}
158
+ totalRows={1}
159
+ pagination={false}
160
+ pageSize={10}
161
+ selection={null}
162
+ showDownload={true}
163
+ showFilters={false}
164
+ showColumnSummaries={false}
165
+ showDataTypes={false}
166
+ showPageSizeSelector={false}
167
+ showColumnExplorer={false}
168
+ showRowExplorer={false}
169
+ showChartBuilder={false}
170
+ rowHeaders={[]}
171
+ fieldTypes={undefined}
172
+ totalColumns={1}
173
+ maxColumns="all"
174
+ hasStableRowId={false}
175
+ lazy={false}
176
+ host={host}
177
+ showSearch={false}
178
+ value={[]}
179
+ setValue={vi.fn()}
180
+ data={data}
181
+ search={vi.fn().mockResolvedValue({
182
+ data,
183
+ total_rows: 1,
184
+ cell_styles: null,
185
+ cell_hover_texts: null,
186
+ })}
187
+ download_as={vi.fn()}
188
+ download_geojson={vi.fn()}
189
+ get_export_metadata={getExportMetadata}
190
+ get_column_summaries={vi.fn()}
191
+ get_data_url={vi.fn() as GetDataUrl}
192
+ get_row_ids={vi.fn() as GetRowIds}
193
+ get_size_bytes={vi.fn().mockResolvedValue({ size_bytes: null })}
194
+ />
195
+ </TooltipProvider>
196
+ </Provider>
197
+ );
198
+ const { rerender } = render(
199
+ view(JSON.stringify([{ before: "POINT (1 2)" }])),
200
+ );
201
+ await waitFor(() =>
202
+ expect(
203
+ screen.getByRole("columnheader", { name: "before" }),
204
+ ).toBeInTheDocument(),
205
+ );
206
+ fireEvent.click(screen.getByTestId("export-button"));
207
+ await waitFor(() =>
208
+ expect(screen.getByTestId("export-summary-parquet")).toHaveTextContent(
209
+ "before",
210
+ ),
211
+ );
212
+
213
+ rerender(view(JSON.stringify([{ after: "POINT (3 4)" }])));
214
+
215
+ await waitFor(() =>
216
+ expect(screen.getByTestId("export-summary-parquet")).toHaveTextContent(
217
+ "after",
218
+ ),
219
+ );
220
+ expect(getExportMetadata).toHaveBeenCalledTimes(2);
221
+ });
222
+
139
223
  it("does not log duplicate keys when a pivot becomes empty", async () => {
140
224
  const host = document.createElement("div");
141
225
  const consoleError = vi
@@ -1,6 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { act, render } from "@testing-library/react";
3
+ import { act, fireEvent, render } from "@testing-library/react";
4
+ import { useState } from "react";
4
5
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
6
  import type { z } from "zod";
6
7
  import { initialModeAtom } from "@/core/mode";
@@ -474,4 +475,161 @@ describe("NumberPlugin", () => {
474
475
  );
475
476
  expect(numberField?.classList.contains("w-full")).toBe(true);
476
477
  });
478
+
479
+ describe("does not snap values to step (#9106)", () => {
480
+ // Feeds `setValue` back into `value`, as the kernel round-trip does.
481
+ const renderNumber = (
482
+ initialValue: number,
483
+ data: Pick<z.infer<NumberPlugin["validator"]>, "start" | "stop" | "step">,
484
+ { debounce = false } = {},
485
+ ) => {
486
+ const plugin = new NumberPlugin();
487
+ const setValue = vi.fn();
488
+ const Harness = () => {
489
+ const [value, setState] = useState<number | null>(initialValue);
490
+ return plugin.render({
491
+ host: document.createElement("div"),
492
+ value,
493
+ setValue: (next) => {
494
+ setValue(next);
495
+ setState(next);
496
+ },
497
+ data: {
498
+ ...data,
499
+ label: null,
500
+ debounce,
501
+ fullWidth: false,
502
+ },
503
+ functions: {},
504
+ });
505
+ };
506
+ const { getByRole } = render(<Harness />);
507
+ act(() => {
508
+ vi.advanceTimersByTime(0);
509
+ });
510
+ const input = getByRole("textbox", {
511
+ name: "Number input",
512
+ }) as HTMLInputElement;
513
+ return { input, setValue };
514
+ };
515
+
516
+ const typeAndCommit = (input: HTMLInputElement, text: string) => {
517
+ act(() => {
518
+ input.focus();
519
+ });
520
+ fireEvent.change(input, { target: { value: text } });
521
+ act(() => {
522
+ input.blur();
523
+ });
524
+ };
525
+
526
+ it.each([
527
+ { value: 2.23, start: 1.255_57, step: 0.001 },
528
+ { value: 2.233_45, start: 1, step: 0.001 },
529
+ ])(
530
+ "displays $value as-is with start=$start and step=$step",
531
+ ({ value, start, step }) => {
532
+ const { input } = renderNumber(value, { start, stop: 20, step });
533
+ expect(input.value).toBe(String(value));
534
+ },
535
+ );
536
+
537
+ it.each([
538
+ { typed: "2.3377", start: 1, step: 0.01 },
539
+ { typed: "2.23111", start: 1.222, step: 0.01 },
540
+ ])(
541
+ "keeps typed value $typed with start=$start and step=$step",
542
+ ({ typed, start, step }) => {
543
+ const { input, setValue } = renderNumber(2.233_45, {
544
+ start,
545
+ stop: 20,
546
+ step,
547
+ });
548
+ typeAndCommit(input, typed);
549
+ expect(setValue).toHaveBeenLastCalledWith(Number(typed));
550
+ expect(input.value).toBe(typed);
551
+ expect(input.getAttribute("aria-invalid")).not.toBe("true");
552
+ // React Aria sets a custom validity on this text input in native mode,
553
+ // which would block `mo.ui.form` submission.
554
+ expect(input.validity.valid).toBe(true);
555
+ },
556
+ );
557
+
558
+ it("clamps typed values to start and stop", () => {
559
+ const { input, setValue } = renderNumber(5, {
560
+ start: 2,
561
+ stop: 10,
562
+ step: 1,
563
+ });
564
+ typeAndCommit(input, "25");
565
+ expect(setValue).toHaveBeenLastCalledWith(10);
566
+ expect(input.value).toBe("10");
567
+ typeAndCommit(input, "1");
568
+ expect(setValue).toHaveBeenLastCalledWith(2);
569
+ expect(input.value).toBe("2");
570
+ });
571
+
572
+ it("shows the bound when typing past a bound it is already at", () => {
573
+ const { input, setValue } = renderNumber(10, {
574
+ start: 2,
575
+ stop: 10,
576
+ step: 1,
577
+ });
578
+ typeAndCommit(input, "50");
579
+ expect(setValue).not.toHaveBeenCalled();
580
+ expect(input.value).toBe("10");
581
+ });
582
+
583
+ it("increments and decrements by step", () => {
584
+ const { input, setValue } = renderNumber(5, {
585
+ start: 0,
586
+ stop: 10,
587
+ step: 0.5,
588
+ });
589
+ fireEvent.keyDown(input, { key: "ArrowUp" });
590
+ expect(setValue).toHaveBeenLastCalledWith(5.5);
591
+ expect(input.value).toBe("5.5");
592
+ fireEvent.keyDown(input, { key: "ArrowDown" });
593
+ expect(setValue).toHaveBeenLastCalledWith(5);
594
+ expect(input.value).toBe("5");
595
+ });
596
+
597
+ it.each([
598
+ { key: "ArrowUp", expected: 3 },
599
+ { key: "ArrowDown", expected: 2 },
600
+ ])(
601
+ "$key from an off-step value moves to the nearest step ($expected)",
602
+ ({ key, expected }) => {
603
+ const { input, setValue } = renderNumber(2.23, {
604
+ start: 0,
605
+ stop: 10,
606
+ step: 1,
607
+ });
608
+ fireEvent.keyDown(input, { key });
609
+ expect(setValue).toHaveBeenLastCalledWith(expected);
610
+ expect(input.value).toBe(String(expected));
611
+ },
612
+ );
613
+
614
+ it.each([
615
+ { typed: "2.3377", expected: 2.3377 },
616
+ { typed: "50", expected: 20 },
617
+ ])(
618
+ "commits typed $typed as $expected after the debounce delay",
619
+ ({ typed, expected }) => {
620
+ const { input, setValue } = renderNumber(
621
+ 2.233_45,
622
+ { start: 1, stop: 20, step: 0.01 },
623
+ { debounce: true },
624
+ );
625
+ typeAndCommit(input, typed);
626
+ expect(setValue).not.toHaveBeenCalled();
627
+ act(() => {
628
+ vi.advanceTimersByTime(200);
629
+ });
630
+ expect(setValue).toHaveBeenLastCalledWith(expected);
631
+ expect(input.value).toBe(String(expected));
632
+ },
633
+ );
634
+ });
477
635
  });
@@ -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");
@@ -165,6 +165,44 @@ export function dateToLocalISOTime(d: Date): string {
165
165
  return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
166
166
  }
167
167
 
168
+ /**
169
+ * Parse a time string into a Date anchored at the Unix epoch day. Supports
170
+ * the backend's wire format (`HH:MM:SS`, as produced by `dateToLocalISOTime`)
171
+ * as well as looser pasted text: optional seconds, optional milliseconds,
172
+ * and an optional `am`/`pm` suffix (e.g. `"14:30"`, `"2:30 PM"`,
173
+ * `"14:30:00.123"`).
174
+ */
175
+ export function parseTimeToDate(value: string): Date | undefined {
176
+ const match = /^(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\.(\d+))?\s*(am|pm)?$/i.exec(
177
+ value.trim(),
178
+ );
179
+ if (!match) {
180
+ return undefined;
181
+ }
182
+ const [, hStr, mStr, sStr, msStr, ampm] = match;
183
+ let hour = Number.parseInt(hStr, 10);
184
+ const minute = Number.parseInt(mStr, 10);
185
+ const second = sStr ? Number.parseInt(sStr, 10) : 0;
186
+ const ms = msStr ? Number.parseInt(msStr.padEnd(3, "0").slice(0, 3), 10) : 0;
187
+ if (minute > 59 || second > 59) {
188
+ return undefined;
189
+ }
190
+ if (ampm) {
191
+ if (hour < 1 || hour > 12) {
192
+ return undefined;
193
+ }
194
+ const isPm = ampm.toLowerCase() === "pm";
195
+ if (hour === 12) {
196
+ hour = isPm ? 12 : 0;
197
+ } else if (isPm) {
198
+ hour += 12;
199
+ }
200
+ } else if (hour > 23) {
201
+ return undefined;
202
+ }
203
+ return new Date(1970, 0, 1, hour, minute, second, ms);
204
+ }
205
+
168
206
  /**
169
207
  * Format a Date as `YYYY-MM-DDTHH:MM:SS` (no timezone suffix) using local
170
208
  * fields. See `dateToLocalISODate` for the rationale on local vs UTC.
@@ -173,6 +211,62 @@ export function dateToLocalISODateTime(d: Date): string {
173
211
  return `${dateToLocalISODate(d)}T${dateToLocalISOTime(d)}`;
174
212
  }
175
213
 
214
+ /**
215
+ * Parse a `YYYY-MM-DD` string into a Date anchored at local midnight.
216
+ *
217
+ * Unlike `new Date("YYYY-MM-DD")`, which the spec parses as UTC midnight,
218
+ * this treats the string as a calendar date in the viewer's own timezone.
219
+ * Returns undefined if the string doesn't match, or encodes a calendar
220
+ * date that doesn't exist (e.g. month 13, Feb 30), since `Date` would
221
+ * otherwise silently roll those over into a different date.
222
+ */
223
+ export function parseLocalISODate(value: string): Date | undefined {
224
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
225
+ if (!match) {
226
+ return undefined;
227
+ }
228
+ const [, yStr, mStr, dStr] = match;
229
+ const year = Number.parseInt(yStr, 10);
230
+ const month = Number.parseInt(mStr, 10);
231
+ const day = Number.parseInt(dStr, 10);
232
+ const date = new Date(year, month - 1, day);
233
+ if (
234
+ date.getFullYear() !== year ||
235
+ date.getMonth() !== month - 1 ||
236
+ date.getDate() !== day
237
+ ) {
238
+ return undefined;
239
+ }
240
+ return date;
241
+ }
242
+
243
+ /**
244
+ * Parse an ISO-ish `YYYY-MM-DD[T ]HH:MM[:SS[.sss]][Z|+HH:MM]` datetime string
245
+ * as wall-clock local time, ignoring any trailing `Z` or UTC offset.
246
+ *
247
+ * This keeps pasted datetimes consistent with the date/time pickers' local-
248
+ * time basis: pasting `2024-01-15T08:30:00Z` displays `08:30` instead of
249
+ * being shifted by the viewer's timezone.
250
+ */
251
+ export function parseLocalISODateTime(value: string): Date | undefined {
252
+ const match =
253
+ /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?$/.exec(
254
+ value.trim(),
255
+ );
256
+ if (!match) {
257
+ return undefined;
258
+ }
259
+ const [, yStr, moStr, dStr, hStr, miStr, sStr] = match;
260
+ return new Date(
261
+ Number.parseInt(yStr, 10),
262
+ Number.parseInt(moStr, 10) - 1,
263
+ Number.parseInt(dStr, 10),
264
+ Number.parseInt(hStr, 10),
265
+ Number.parseInt(miStr, 10),
266
+ sStr ? Number.parseInt(sStr, 10) : 0,
267
+ );
268
+ }
269
+
176
270
  export const supportedDateFormats = ["yyyy", "yyyy-MM", "yyyy-MM-dd"] as const;
177
271
  export type DateFormat = (typeof supportedDateFormats)[number];
178
272
 
@@ -1 +0,0 @@
1
- import{t as e}from"./worker-Bf8dt6S-.js";var t=e(((e,t)=>{t.exports={}}));export default t();