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

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 (158) hide show
  1. package/dist/{ConnectedDataExplorerComponent-BZmqc_F0.js → ConnectedDataExplorerComponent-CNSxdKyJ.js} +15 -15
  2. package/dist/{ErrorBoundary-CXkr_IcB.js → ErrorBoundary-Cg59O3xL.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-DQDjNXt3.js} +3 -3
  6. package/dist/{any-language-editor-BFzqhOBR.js → any-language-editor-B9REGlD9.js} +7 -7
  7. package/dist/{architectureDiagram-5GKGNRK7-BKMataHB.js → architectureDiagram-5GKGNRK7-Nmls7Wxn.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/{cells-DZi0ioak.js → cells-C8iCYvLU.js} +6644 -5604
  15. package/dist/{channel-BxCkvUgE.js → channel-CPObtJXj.js} +1 -1
  16. package/dist/{chat-ui-CwOWzoSO.js → chat-ui-BDpymeoe.js} +1589 -1396
  17. package/dist/{check-CNP79M3A.js → check-EDwSwP89.js} +1 -1
  18. package/dist/{chunk-4HAMMTFA-CH7Gpamd.js → chunk-4HAMMTFA-D4eFPyFh.js} +5 -5
  19. package/dist/{chunk-75Z2AOVW-AI7CvgKn.js → chunk-75Z2AOVW-D7gxIs2Q.js} +1 -1
  20. package/dist/{chunk-DU6HZSFF-BSvuTkX4.js → chunk-DU6HZSFF-BwiyaNpg.js} +1 -1
  21. package/dist/{chunk-F27PBJKO-CLshwT9T.js → chunk-F27PBJKO-ufseIn5y.js} +1 -1
  22. package/dist/{chunk-GMAD6QVW-wnHcn-lH.js → chunk-GMAD6QVW-BhaJx0X3.js} +3 -3
  23. package/dist/{chunk-GVQU2GXP-7O2p8hut.js → chunk-GVQU2GXP-Cwbt-lF0.js} +2 -2
  24. package/dist/{chunk-IMKFNOWR-Bu0Vh_Yi.js → chunk-IMKFNOWR-CoIFEo-1.js} +5 -5
  25. package/dist/{chunk-L3NEJ4N5-B9_Alrxb.js → chunk-L3NEJ4N5-n-zgIVSy.js} +5 -5
  26. package/dist/{chunk-OSK3NFVY-PeQPH2fD.js → chunk-OSK3NFVY-DXa-Cxbn.js} +6 -6
  27. package/dist/{chunk-P2QGCYS3-D9twiFuU.js → chunk-P2QGCYS3-B_Zd3-X0.js} +1 -1
  28. package/dist/{chunk-POPQ4Y6H-BUiNs18s.js → chunk-POPQ4Y6H-hYOXbYH_.js} +1 -1
  29. package/dist/{chunk-PWAF6VOD-D8S9NPCy.js → chunk-PWAF6VOD-CQw5qATZ.js} +1 -1
  30. package/dist/{chunk-SHT3W25Y-B2dyNZ31.js → chunk-SHT3W25Y-lHiGx3ZC.js} +7 -7
  31. package/dist/{chunk-SVP7TREG-D7UPvB10.js → chunk-SVP7TREG-BvsHIrY_.js} +2 -2
  32. package/dist/{chunk-TICWLB2K-BNaGoadd.js → chunk-TICWLB2K-BC8YIm_n.js} +5 -5
  33. package/dist/{chunk-YOKDWASO-DrWbKnxA.js → chunk-YOKDWASO-DPZk8vhS.js} +6 -6
  34. package/dist/classDiagram-ZZMXUADV-Bt98lkkv.js +30 -0
  35. package/dist/classDiagram-v2-VYDZK3BY-D9bfbcC3.js +30 -0
  36. package/dist/{code-visibility-pPjYsRTH.js → code-visibility-Da3iitn_.js} +8 -8
  37. package/dist/{common-CkvBp0B4.js → common-C6FHcu5p.js} +6574 -7877
  38. package/dist/{compiler-runtime-Cj8ZWiNS.js → compiler-runtime-CldRdSsv.js} +2 -2
  39. package/dist/{copy-icon-BY7jgcaV.js → copy-icon-Ft7t9bqz.js} +6 -6
  40. package/dist/{cynefinDiagram-5FMLGOSQ-CCDn4Z9e.js → cynefinDiagram-5FMLGOSQ-CgWra7wt.js} +3 -3
  41. package/dist/{dagre-GXQ25YYZ-BJiUO_SV.js → dagre-GXQ25YYZ-CFZ8-35c.js} +10 -10
  42. package/dist/{data-grid-overlay-editor-L6ELxFAh.js → data-grid-overlay-editor-mJt3Pg4S.js} +7 -7
  43. package/dist/{data-grid-types-BucVmDDp.js → data-grid-types-BH_r7g7l.js} +1 -1
  44. package/dist/{diagram-S7CK7UJ4-BZfxFNRR.js → diagram-S7CK7UJ4-lCNQrW7d.js} +4 -4
  45. package/dist/{diagram-UQ7AKVKN-1xcsiIMg.js → diagram-UQ7AKVKN-CQPQ6pP_.js} +3 -3
  46. package/dist/{diagram-VSXAHHWV-DPAWJ9Nw.js → diagram-VSXAHHWV-BJWcVVJY.js} +2 -2
  47. package/dist/{diagram-VX7I27RA-DmRsnHYY.js → diagram-VX7I27RA-Cf3b0llz.js} +5 -5
  48. package/dist/{diagram-Z3DM3KII--b49ryKx.js → diagram-Z3DM3KII-Bt4QLrpn.js} +3 -3
  49. package/dist/{dialog-CmExhUDI.js → dialog-CEBFZNyc.js} +6 -6
  50. package/dist/{dist-CcyKTBTF.js → dist-DpBpA_n1.js} +2 -2
  51. package/dist/{dropdown-menu-Cz4TdOJh.js → dropdown-menu-CP_qCYHC.js} +139 -139
  52. package/dist/{ebnfDiagram-PWID7BFC-rSwVyh4y.js → ebnfDiagram-PWID7BFC-D3NAyPFY.js} +3 -3
  53. package/dist/{erDiagram-RLTQ6QDP-Bfd3iAoC.js → erDiagram-RLTQ6QDP-Tlu09_Nm.js} +12 -12
  54. package/dist/{error-banner-D30nA9nE.js → error-banner-Da600cwz.js} +6 -6
  55. package/dist/{error-entries-BovkFYNy.js → error-entries-CzoYkIQZ.js} +2 -2
  56. package/dist/{esm-DAVrGVKP.js → esm-AMFssoNp.js} +4 -4
  57. package/dist/{feedback-button-D-pV0Qji.js → feedback-button-DaPwHIdM.js} +13 -13
  58. package/dist/flowDiagram-HODETNUW-CAYbpJmV.js +16 -0
  59. package/dist/{formats-DhhbdyAH.js → formats-BGh2thRc.js} +6 -6
  60. package/dist/{ganttDiagram-EL5Y4UJY-B4m_YsKa.js → ganttDiagram-EL5Y4UJY-BQT16JSc.js} +2 -2
  61. package/dist/{gitGraphDiagram-WWUBYQGX-QRBbvWXh.js → gitGraphDiagram-WWUBYQGX-D5nSIzdE.js} +2 -2
  62. package/dist/{glide-data-editor-D6CfYwdr.js → glide-data-editor-Ctz2mH6k.js} +60 -60
  63. package/dist/{glide-utils-BVJ4fkxX.js → glide-utils-DJyqh0EM.js} +4 -4
  64. package/dist/{globals-BwK9YGrX.js → globals-EpFoRj_E.js} +3 -3
  65. package/dist/{highlighted-body-KPVGNVTW-Ad3MKZ18.js → highlighted-body-KPVGNVTW-zwjq21tf.js} +4 -4
  66. package/dist/{html-to-image-Cn1_16-x.js → html-to-image-CZY1pIZH.js} +17 -17
  67. package/dist/{infoDiagram-27XIBGKW-DVQFYvhg.js → infoDiagram-27XIBGKW-XrDxhr0x.js} +2 -2
  68. package/dist/{ishikawaDiagram-5VMMS53U-viPND-nm.js → ishikawaDiagram-5VMMS53U-CD1muzov.js} +3 -3
  69. package/dist/{journeyDiagram-3NMN7TZE-BeeJdJo3.js → journeyDiagram-3NMN7TZE-BaLqn-dZ.js} +2 -2
  70. package/dist/{jsx-runtime-DgDPJV9S.js → jsx-runtime-3BD1kT0s.js} +1 -1
  71. package/dist/{kanban-definition-UXKFOSKX-Dcl_ECO9.js → kanban-definition-UXKFOSKX-Dg9W142_.js} +9 -9
  72. package/dist/{katex-BNCmKUol.js → katex-BIdZGsyO.js} +141 -116
  73. package/dist/{label-DQO1jFjf.js → label-BDKF3Afc.js} +10 -10
  74. package/dist/{loader-Cm-ATtou.js → loader-BQOfjTIJ.js} +1 -1
  75. package/dist/main.js +499 -467
  76. package/dist/{mermaid-BfhcnSj-.js → mermaid-CiAXjLpA.js} +56 -56
  77. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +2 -0
  78. package/dist/{mhchem-BEc7A1or.js → mhchem-CdWPJK6g.js} +1 -1
  79. package/dist/{mindmap-definition-YA3MSWOX-BdxxhF-5.js → mindmap-definition-YA3MSWOX-DgCidgYJ.js} +11 -11
  80. package/dist/{number-overlay-editor-tMylyldi.js → number-overlay-editor-CtxrummL.js} +5 -5
  81. package/dist/{pegDiagram-XKGWAZYB-Du5p2s0w.js → pegDiagram-XKGWAZYB-coOYYKB6.js} +3 -3
  82. package/dist/{pieDiagram-E7YTZNPT-DJg-SOIq.js → pieDiagram-E7YTZNPT-BnXTc3bj.js} +3 -3
  83. package/dist/{process-output-CUG57KRp.js → process-output-e4jj9Ijt.js} +4 -4
  84. package/dist/{quadrantDiagram-AXDQQJYC-keLfFx9f.js → quadrantDiagram-AXDQQJYC-Ccw0xMjN.js} +1 -1
  85. package/dist/{railroadDiagram-O6MQD6OU-C0ojm-6_.js → railroadDiagram-O6MQD6OU-B28-u83n.js} +3 -3
  86. package/dist/{react-dom-BUgJ-ab_.js → react-dom-DUmH0GXv.js} +29 -21
  87. package/dist/{react-ofJbxM4x.js → react-sHtZdjeP.js} +103 -94
  88. package/dist/{react-vega-ewj7q_U5.js → react-vega-BED0DjiB.js} +3 -3
  89. package/dist/{requirementDiagram-BXWQKSXE-CNwNBmI3.js → requirementDiagram-BXWQKSXE-CdYCTMu0.js} +11 -11
  90. package/dist/{reveal-component-LXUMcqWX.js → reveal-component-DrIXvR_7.js} +906 -878
  91. package/dist/{sankeyDiagram-P5KCCOFB-Bh4EsJox.js → sankeyDiagram-P5KCCOFB-ELvOcnZH.js} +1 -1
  92. package/dist/{select-Bs-nmvm6.js → select-BButFi-d.js} +11 -11
  93. package/dist/{sequenceDiagram-WJ2MYXX4-DQaW5qBG.js → sequenceDiagram-WJ2MYXX4-CM3aRAe_.js} +4 -4
  94. package/dist/{spec-DVsoMvYT.js → spec-BwarBpUJ.js} +4 -4
  95. package/dist/{stateDiagram-D77RDMKH-BDa515QC.js → stateDiagram-D77RDMKH-DOZUMVj8.js} +13 -13
  96. package/dist/stateDiagram-v2-MP3YSRHH-CISYxma6.js +30 -0
  97. package/dist/style.css +1 -1
  98. package/dist/{swimlanes-42K2YHIH-M4IdGi8J.js → swimlanes-42K2YHIH-BkSppxp8.js} +10 -10
  99. package/dist/swimlanesDiagram-VR7AAH4N-c8G5pGxC.js +30 -0
  100. package/dist/{swiper-component-CpN-hewZ.js → swiper-component-DKntyn8p.js} +5 -5
  101. package/dist/{timeline-definition-24CTP7MA-DH-kvakg.js → timeline-definition-24CTP7MA-CaMQayqK.js} +3 -3
  102. package/dist/{toDate-vEi5kTfY.js → toDate-BNJf01u1.js} +7 -7
  103. package/dist/{toast-DfEpJedt.js → toast-B9UMEi8u.js} +9 -9
  104. package/dist/{tooltip-CuNWg2ay.js → tooltip-BO1REbqs.js} +7 -7
  105. package/dist/{use-restore-focus-kmgwrL8q.js → use-restore-focus-BEM7sFWl.js} +2042 -1827
  106. package/dist/{use-vega-container-remeasure-4DwJbPWu.js → use-vega-container-remeasure-Bz4PZMtt.js} +2 -2
  107. package/dist/{useAsyncData-rkAlR_vh.js → useAsyncData-Deh5kszT.js} +3 -3
  108. package/dist/{useDateFormatter-C-tz65rD.js → useDateFormatter-D8XruNGG.js} +4 -4
  109. package/dist/{useDeepCompareMemoize-BlHFBj5_.js → useDeepCompareMemoize-DXwzRINI.js} +2 -2
  110. package/dist/{useIframeCapabilities-C1Bt04Iz.js → useIframeCapabilities-DBETvvPl.js} +2 -2
  111. package/dist/{useLifecycle-B68iY077.js → useLifecycle-j7D9uD1o.js} +6 -6
  112. package/dist/{useTheme-BwSgeAFb.js → useTheme-CRnjkku0.js} +7 -7
  113. package/dist/{vega-component-C-bPttRN.js → vega-component-DG29TAB7.js} +14 -14
  114. package/dist/{vega-embed-container-sSR5hAsy.js → vega-embed-container-BNHE18G9.js} +5 -5
  115. package/dist/{vennDiagram-4TSXK5OY-d5g7XeiI.js → vennDiagram-4TSXK5OY-CF8pfaK3.js} +3 -3
  116. package/dist/{wardleyDiagram-VM6X3IG4-BfX3bSzR.js → wardleyDiagram-VM6X3IG4-dGPu39d-.js} +3 -3
  117. package/dist/{xychartDiagram-S5SC5T6Z-C1QsLZg7.js → xychartDiagram-S5SC5T6Z-CP6-9q56.js} +5 -5
  118. package/package.json +21 -21
  119. package/src/components/data-table/__tests__/column_formatting.test.ts +30 -0
  120. package/src/components/data-table/__tests__/columns.test.tsx +70 -0
  121. package/src/components/data-table/column-formatting/feature.ts +20 -1
  122. package/src/components/data-table/columns.tsx +25 -18
  123. package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +4 -3
  124. package/src/components/editor/output/console/ConsoleOutput.tsx +5 -3
  125. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +18 -0
  126. package/src/components/storage/__tests__/storage-file-viewer.test.tsx +2 -1
  127. package/src/core/cells/__tests__/document-roundtrip.test.ts +16 -0
  128. package/src/core/codemirror/copilot/__tests__/completion-integration.test.ts +447 -0
  129. package/src/core/codemirror/copilot/__tests__/inline-completion.test.ts +395 -0
  130. package/src/core/codemirror/copilot/__tests__/language-server.test.ts +511 -2
  131. package/src/core/codemirror/copilot/__tests__/rpc-cancellation.test.ts +201 -0
  132. package/src/core/codemirror/copilot/extension.ts +22 -11
  133. package/src/core/codemirror/copilot/language-server.ts +109 -60
  134. package/src/core/codemirror/language/__tests__/sql.test.ts +4 -3
  135. package/src/core/codemirror/language/embedded/__tests__/embedded-python.test.ts +138 -26
  136. package/src/core/codemirror/language/embedded/embedded-python.ts +47 -13
  137. package/src/core/codemirror/language/embedded/latex.ts +20 -13
  138. package/src/core/datasets/__tests__/data-source-discovery.test.ts +18 -11
  139. package/src/core/islands/bridge.ts +2 -2
  140. package/src/core/kernel/messages.ts +4 -7
  141. package/src/core/network/__tests__/api.test.ts +114 -1
  142. package/src/core/network/api.ts +3 -2
  143. package/src/core/wasm/bridge.ts +2 -2
  144. package/src/core/wasm/worker/types.ts +2 -2
  145. package/src/css/katex-css.test.ts +1 -1
  146. package/src/css/katex.min.css +6 -1
  147. package/src/hooks/__tests__/useEventListener.test.tsx +45 -1
  148. package/src/hooks/useEventListener.ts +6 -1
  149. package/src/plugins/impl/NumberPlugin.tsx +20 -3
  150. package/src/plugins/impl/__tests__/NumberPlugin.test.tsx +159 -1
  151. package/dist/assets/__vite-browser-external-BqAtzd2P.js +0 -1
  152. package/dist/assets/worker-Bf8dt6S-.js +0 -109
  153. package/dist/classDiagram-ZZMXUADV-Bbo2n384.js +0 -30
  154. package/dist/classDiagram-v2-VYDZK3BY-xR8cGP0r.js +0 -30
  155. package/dist/flowDiagram-HODETNUW-D7IBKS12.js +0 -16
  156. package/dist/mermaid-HWGCJPDP-DYCzSILp.js +0 -2
  157. package/dist/stateDiagram-v2-MP3YSRHH-ChNddepZ.js +0 -30
  158. package/dist/swimlanesDiagram-VR7AAH4N-FUSwmzEp.js +0 -30
@@ -32,7 +32,7 @@ describe("copied KaTeX stylesheet", () => {
32
32
  if (change === "added") {
33
33
  root.append(
34
34
  postcss.parse(
35
- ".katex .reflectbox { display: inline-block; transform: scaleX(-1); }",
35
+ ".katex .upstream-drift-probe { display: inline-block; }",
36
36
  ),
37
37
  );
38
38
  } else {
@@ -35,7 +35,7 @@
35
35
  }
36
36
 
37
37
  .katex .katex-version::after {
38
- content: "0.18.5";
38
+ content: "0.18.7";
39
39
  }
40
40
 
41
41
  .katex .katex-mathml {
@@ -1131,6 +1131,11 @@
1131
1131
  padding: 0 0.03889em;
1132
1132
  }
1133
1133
 
1134
+ .katex .reflectbox {
1135
+ display: inline-block;
1136
+ transform: scaleX(-1);
1137
+ }
1138
+
1134
1139
  .katex .eqn-num::before {
1135
1140
  content: "(" counter(katexEqnNo) ")";
1136
1141
  counter-increment: katexEqnNo;
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { renderHook } from "@testing-library/react";
4
4
  import { useRef } from "react";
5
- import { beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
6
  import {
7
7
  type HTMLElementNotDerivedFromRef,
8
8
  isRefObject,
@@ -28,6 +28,50 @@ describe("isRefObject", () => {
28
28
  });
29
29
  });
30
30
 
31
+ describe.each([1, 2])("with %i anchors named current", (count) => {
32
+ let iframe: HTMLIFrameElement;
33
+ let browserWindow: Window;
34
+
35
+ beforeEach(() => {
36
+ // An iframe preserves named properties hidden by Vitest's global window.
37
+ iframe = document.createElement("iframe");
38
+ document.body.append(iframe);
39
+ if (!iframe.contentWindow) {
40
+ throw new Error("Expected an iframe window");
41
+ }
42
+ browserWindow = iframe.contentWindow;
43
+ const anchors = Array.from({ length: count }, () => {
44
+ const anchor = browserWindow.document.createElement("a");
45
+ anchor.id = "current";
46
+ return anchor;
47
+ });
48
+ browserWindow.document.body.append(...anchors);
49
+ });
50
+
51
+ afterEach(() => {
52
+ iframe.remove();
53
+ });
54
+
55
+ it("does not mistake window for a React ref", () => {
56
+ expect("current" in browserWindow).toBe(true);
57
+ expect(isRefObject(browserWindow)).toBe(false);
58
+ });
59
+
60
+ it("attaches and removes the listener on window", () => {
61
+ const listener = vi.fn();
62
+ const { unmount } = renderHook(() =>
63
+ useEventListener(browserWindow, "resize", listener),
64
+ );
65
+
66
+ browserWindow.dispatchEvent(new Event("resize"));
67
+ expect(listener).toHaveBeenCalledTimes(1);
68
+
69
+ unmount();
70
+ browserWindow.dispatchEvent(new Event("resize"));
71
+ expect(listener).toHaveBeenCalledTimes(1);
72
+ });
73
+ });
74
+
31
75
  describe("useEventListener", () => {
32
76
  beforeEach(() => {
33
77
  vi.clearAllMocks();
@@ -13,7 +13,12 @@ export type HTMLElementNotDerivedFromRef<T = HTMLElement> = T & {
13
13
  };
14
14
 
15
15
  export function isRefObject<T>(target: unknown): target is RefObject<T | null> {
16
- return target !== null && typeof target === "object" && "current" in target;
16
+ return (
17
+ target !== null &&
18
+ typeof target === "object" &&
19
+ !("addEventListener" in target) &&
20
+ "current" in target
21
+ );
17
22
  }
18
23
 
19
24
  export function useEventListener<K extends keyof DocumentEventMap>(
@@ -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"}
@@ -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
  });
@@ -1 +0,0 @@
1
- import{t as e}from"./worker-Bf8dt6S-.js";var t=e(((e,t)=>{t.exports={}}));export default t();