@marimo-team/islands 0.23.10-dev5 → 0.23.10-dev50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ConnectedDataExplorerComponent-CyV83R2m.js → ConnectedDataExplorerComponent-Z3RP7Vmm.js} +31 -31
- package/dist/{ErrorBoundary-rULOrC_p.js → ErrorBoundary-DpbaKVv7.js} +1 -1
- package/dist/{any-language-editor-DfdpyDv_.js → any-language-editor-CAgFD4Kd.js} +20 -20
- package/dist/assets/__vite-browser-external-eshhtsgZ.js +1 -0
- package/dist/assets/worker-CC0Oul9k.js +73 -0
- package/dist/{chat-ui-1rLrACXD.js → chat-ui-BxwraqS-.js} +77 -77
- package/dist/{check-DTbrK0zt.js → check-BCaJeT-J.js} +1 -1
- package/dist/{chunk-5FQGJX7Z-BNjes6Yx.js → chunk-5FQGJX7Z-BbqSm5gU.js} +1227 -1222
- package/dist/{code-block-37QAKDTI-m92Yc8pv.js → code-block-37QAKDTI-O6IH6wAi.js} +1 -1
- package/dist/{code-visibility-Dek9JNl9.js → code-visibility-DKAnHkUo.js} +2166 -1292
- package/dist/{copy-BuQpJEzp.js → copy-UqRYxiOg.js} +33 -33
- package/dist/dist-7QfXoMdB.js +5 -0
- package/dist/{dist-DgnE8F-r.js → dist-A2846XWO.js} +1 -1
- package/dist/dist-BEXXyZig.js +5 -0
- package/dist/{dist-B3pZ0Ab6.js → dist-BR_gyG9L.js} +3 -3
- package/dist/{dist-CcXxepx6.js → dist-BSAt6RhH.js} +27 -27
- package/dist/{dist-Bde4a2kU.js → dist-BY018Paw.js} +8 -8
- package/dist/dist-BYj57OV4.js +5 -0
- package/dist/{dist-CUCNs1ja.js → dist-BaoDKvdy.js} +2 -2
- package/dist/{dist-Cy1WxgBD.js → dist-Bf7SHuNp.js} +5 -5
- package/dist/{dist-Bz_sYWbr.js → dist-Bk75fBZA.js} +2 -2
- package/dist/dist-BlSvQzNr.js +5 -0
- package/dist/{dist-C5VC_yzu.js → dist-BzEzfugY.js} +1 -1
- package/dist/dist-CCBlxAgS.js +8 -0
- package/dist/dist-CIDTVIUf.js +5 -0
- package/dist/{dist-CLUtPrdy.js → dist-CIYBwstr.js} +1 -1
- package/dist/{dist-BotSqB48.js → dist-C_Y3oV3C.js} +12 -12
- package/dist/{dist-BTfv03uy.js → dist-CcWX6tmx.js} +2 -2
- package/dist/{dist-BhM8gdSO.js → dist-CoXAujgg.js} +4 -4
- package/dist/{dist-4j4c7bjm.js → dist-CpxNdDkw.js} +3 -3
- package/dist/dist-CqQyhAM8.js +8 -0
- package/dist/dist-CwRu2Xzh.js +5 -0
- package/dist/{dist-BcuoonNH.js → dist-CxJDU6Bh.js} +9 -9
- package/dist/{dist-DxvORzUR.js → dist-D-W5ny5a.js} +8 -8
- package/dist/dist-D8CDTVgf.js +6 -0
- package/dist/dist-D8DNB0nO.js +8 -0
- package/dist/dist-DL6N_q-A.js +5 -0
- package/dist/{dist-BbbIBDiQ.js → dist-DMjWuVs8.js} +1 -1
- package/dist/dist-DOFbNV_b.js +8 -0
- package/dist/dist-DPrYzMY0.js +6 -0
- package/dist/{dist-h2c8sZvT.js → dist-DZORgqKY.js} +1 -1
- package/dist/{dist-B3P2fFpz.js → dist-DZo4nSS0.js} +14 -14
- package/dist/{dist-D4CewLk6.js → dist-Dax--nl9.js} +1 -1
- package/dist/{dist-DRfcqpxJ.js → dist-DgGbNavJ.js} +2 -2
- package/dist/{dist-C1BYNeCR.js → dist-Dk6PV_d3.js} +10 -10
- package/dist/{dist-fQ0ViXGs.js → dist-Dv_Y15yk.js} +107 -107
- package/dist/{dist-Bfwsv11D.js → dist-DyyjKEYf.js} +2 -2
- package/dist/{dist-p2qyWijU.js → dist-GZXUmt0b.js} +2 -2
- package/dist/{dist-CLJWPTX2.js → dist-LTU8Hdvn.js} +3 -3
- package/dist/{dist-DqAWR3CS.js → dist-M9Vag9Y0.js} +20 -20
- package/dist/{dist-DNdhYsgW.js → dist-U4F-tbMs.js} +79 -62
- package/dist/{dist-RqXTaiir.js → dist-abid3KgM.js} +11 -11
- package/dist/dist-cdmMjgsn.js +5 -0
- package/dist/dist-hT4QzYX-.js +1247 -0
- package/dist/{dist-luvabDEB.js → dist-t9Kf7xqC.js} +2 -2
- package/dist/{error-banner-5bz0L9hS.js → error-banner-CJXYJ6Sb.js} +11 -6
- package/dist/esm-BaH2eg5-.js +1171 -0
- package/dist/{esm-Duie8iU-.js → esm-ga2Bf3O2.js} +43 -43
- package/dist/{extends-BgdxCfYu.js → extends-D_hDsj6R.js} +4 -4
- package/dist/files/wasm-intro.py +11 -8
- package/dist/{formats-DHxc-FdY.js → formats-C4wO47tk.js} +1 -1
- package/dist/{glide-data-editor-BOmK9ETQ.js → glide-data-editor-BPkCPs7L.js} +255 -255
- package/dist/{html-to-image-CnBdHeLh.js → html-to-image-DXSdAI_Y.js} +2338 -2279
- package/dist/{input-_2sjvfne.js → input-OdWHkobi.js} +263 -245
- package/dist/{label-LWtdw5i8.js → label-CC4ytI1X.js} +1 -1
- package/dist/main.js +7631 -7559
- package/dist/{mermaid-4DMBBIKO-DIdL224_.js → mermaid-4DMBBIKO-Eg3D4FVz.js} +1 -1
- package/dist/{mermaid-lXOw5Py9.js → mermaid-BotvIKg9.js} +4 -4
- package/dist/{process-output-vFgqkyUT.js → process-output-uozPQhBl.js} +3 -3
- package/dist/{reveal-component-C8AmnVfj.js → reveal-component-BxE_9kae.js} +698 -620
- package/dist/{spec-B96zNUEA.js → spec-X7FwLJni.js} +4 -4
- package/dist/{strings-Bu3vlb6W.js → strings-J57tzLr3.js} +47 -46
- package/dist/style.css +1 -1
- package/dist/{toDate-x-WRDCH7.js → toDate-d8RCRrRd.js} +2 -2
- package/dist/{tooltip-C5FYOpQc.js → tooltip-DpcyNkQ2.js} +2 -2
- package/dist/{types-CVvp1fKr.js → types-ChtMFmZ2.js} +1 -1
- package/dist/{useAsyncData-iRgKDT5s.js → useAsyncData-PonK__yh.js} +1 -1
- package/dist/{useDateFormatter-BA4FCquG.js → useDateFormatter-QB-3MpYr.js} +2 -2
- package/dist/{useDeepCompareMemoize-CkQ57VS2.js → useDeepCompareMemoize-D3NGWke6.js} +1 -1
- package/dist/{useLifecycle-BBO9PIph.js → useLifecycle-00mO3OSS.js} +2 -2
- package/dist/{useTheme-DHIrRQOe.js → useTheme-DEhDzATN.js} +1 -1
- package/dist/{vega-component-Dq-SH463.js → vega-component-DGPUhbDs.js} +8 -8
- package/dist/{zod-CoBiJ5v4.js → zod-aLSua2NL.js} +24 -23
- package/package.json +4 -3
- package/src/components/chat/chat-display.tsx +2 -2
- package/src/components/data-table/TableBottomBar.tsx +1 -15
- package/src/components/data-table/TableTopBar.tsx +8 -13
- package/src/components/data-table/__tests__/TableBottomBar.test.tsx +6 -12
- package/src/components/data-table/__tests__/column-visibility-dropdown.test.tsx +227 -0
- package/src/components/data-table/__tests__/data-table.test.tsx +154 -12
- package/src/components/data-table/column-visibility-dropdown.tsx +204 -0
- package/src/components/data-table/data-table.tsx +1 -1
- package/src/components/data-table/filter-by-values-picker.tsx +39 -17
- package/src/components/data-table/filter-pills.tsx +1 -1
- package/src/components/data-table/hover-tooltip/__tests__/content.test.ts +60 -0
- package/src/components/data-table/hover-tooltip/content.ts +44 -0
- package/src/components/data-table/hover-tooltip/hover-tooltip.tsx +55 -0
- package/src/components/data-table/hover-tooltip/use-table-hover-tooltip.ts +159 -0
- package/src/components/data-table/renderers.tsx +27 -43
- package/src/components/datasources/__tests__/filter-empty.test.ts +183 -0
- package/src/components/datasources/datasources.tsx +92 -3
- package/src/components/editor/cell/code/language-toggle.tsx +7 -1
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +97 -52
- package/src/components/editor/chrome/wrapper/lazy-panels.ts +91 -0
- package/src/components/editor/chrome/wrapper/sidebar.tsx +2 -0
- package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +9 -2
- package/src/components/editor/connections/storage/__tests__/as-code.test.ts +62 -27
- package/src/components/editor/connections/storage/add-storage-form.tsx +5 -1
- package/src/components/editor/connections/storage/as-code.ts +20 -6
- package/src/components/editor/documentation.css +35 -0
- package/src/components/editor/file-tree/file-explorer.tsx +8 -18
- package/src/components/editor/file-tree/tree-actions.tsx +46 -1
- package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +20 -0
- package/src/components/editor/renderers/slides-layout/types.ts +1 -0
- package/src/components/markdown/__tests__/markdown-renderer.test.tsx +43 -0
- package/src/components/markdown/markdown-renderer.tsx +24 -1
- package/src/components/slides/__tests__/minimap-actions.test.tsx +166 -0
- package/src/components/slides/__tests__/reveal-component.test.ts +425 -0
- package/src/components/slides/minimap.tsx +127 -10
- package/src/components/slides/reveal-component.tsx +287 -61
- package/src/components/slides/slide-cell-view.tsx +26 -2
- package/src/components/slides/slide-form.tsx +26 -4
- package/src/components/storage/__tests__/storage-inspector.test.ts +53 -0
- package/src/components/storage/storage-inspector.tsx +115 -44
- package/src/components/ui/__tests__/use-toast.test.ts +75 -0
- package/src/components/ui/combobox.tsx +51 -32
- package/src/components/ui/number-field.tsx +5 -1
- package/src/components/ui/reorderable-list.tsx +13 -0
- package/src/components/ui/select-core/__tests__/use-select-list.test.ts +294 -0
- package/src/components/ui/select-core/__tests__/utils.test.ts +222 -0
- package/src/components/ui/select-core/index.ts +16 -0
- package/src/components/ui/select-core/option-row.tsx +33 -0
- package/src/components/ui/select-core/render-slot.ts +20 -0
- package/src/components/ui/select-core/select-list.tsx +248 -0
- package/src/components/ui/select-core/types.ts +44 -0
- package/src/components/ui/select-core/use-select-list.ts +347 -0
- package/src/components/ui/select-core/utils.ts +121 -0
- package/src/components/ui/use-toast.ts +33 -13
- package/src/core/codemirror/language/languages/python.ts +2 -0
- package/src/core/codemirror/language/languages/sql/utils.ts +3 -1
- package/src/core/codemirror/lsp/__tests__/markdown-renderer.test.ts +41 -0
- package/src/core/codemirror/lsp/markdown-renderer.ts +59 -0
- package/src/core/datasets/data-source-connections.ts +2 -0
- package/src/core/network/__tests__/requests-static.test.ts +30 -0
- package/src/core/network/requests-static.ts +14 -10
- package/src/core/storage/__tests__/state.test.ts +49 -0
- package/src/core/storage/__tests__/useStorageEntries.test.tsx +199 -3
- package/src/core/storage/state.ts +124 -13
- package/src/core/storage/types.ts +6 -0
- package/src/core/wasm/worker/bootstrap.ts +12 -4
- package/src/plugins/core/__test__/registerReactComponent.test.ts +96 -0
- package/src/plugins/core/registerReactComponent.tsx +42 -10
- package/src/plugins/impl/MultiselectPlugin.tsx +19 -142
- package/src/plugins/impl/SearchableSelect.tsx +16 -97
- package/src/plugins/impl/SliderPlugin.tsx +132 -18
- package/src/plugins/impl/__tests__/DropdownPlugin.test.tsx +5 -2
- package/src/plugins/impl/__tests__/MultiSelectPlugin.test.ts +1 -1
- package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +278 -8
- package/src/plugins/layout/DownloadPlugin.tsx +1 -1
- package/src/utils/__tests__/numbers.test.ts +20 -0
- package/src/utils/errors.ts +9 -0
- package/src/utils/lazy.ts +6 -1
- package/src/utils/numbers.ts +27 -0
- package/dist/assets/__vite-browser-external-Ci2ZQfXU.js +0 -1
- package/dist/assets/worker-ip3AI_sN.js +0 -73
- package/dist/dist-0Fif7jnk.js +0 -5
- package/dist/dist-B5h_9sHB.js +0 -6
- package/dist/dist-B9M6R5ye.js +0 -5
- package/dist/dist-BCt3tnck.js +0 -8
- package/dist/dist-BUIJwMwn.js +0 -8
- package/dist/dist-BpquMd3k.js +0 -5
- package/dist/dist-BzJsqYfz.js +0 -5
- package/dist/dist-CA5ELXAf.js +0 -6
- package/dist/dist-CLBRs6Uv.js +0 -5
- package/dist/dist-CStVCMbq.js +0 -5
- package/dist/dist-CZRIEY3Y.js +0 -8
- package/dist/dist-CuUHbFD0.js +0 -5
- package/dist/dist-DV7Iabxb.js +0 -8
- package/dist/dist-DhHh0jLg.js +0 -1247
- package/dist/dist-DuEeHMvL.js +0 -5
- package/dist/esm-BfhQmZjp.js +0 -1171
- package/src/plugins/impl/multiselectFilterFn.tsx +0 -22
- /package/src/components/{data-table → ui}/value-chips.tsx +0 -0
|
@@ -50,11 +50,18 @@ describe("SliderPlugin", () => {
|
|
|
50
50
|
vi.useRealTimers();
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
const createProps = (
|
|
54
|
-
debounce
|
|
55
|
-
includeInput
|
|
56
|
-
setValue
|
|
57
|
-
|
|
53
|
+
const createProps = ({
|
|
54
|
+
debounce,
|
|
55
|
+
includeInput,
|
|
56
|
+
setValue,
|
|
57
|
+
}: {
|
|
58
|
+
debounce: boolean;
|
|
59
|
+
includeInput: boolean;
|
|
60
|
+
setValue: ReturnType<typeof vi.fn>;
|
|
61
|
+
}): IPluginProps<
|
|
62
|
+
number,
|
|
63
|
+
z.infer<typeof SliderPlugin.prototype.validator>
|
|
64
|
+
> => {
|
|
58
65
|
return {
|
|
59
66
|
host: document.createElement("div"),
|
|
60
67
|
value: 5,
|
|
@@ -76,10 +83,57 @@ describe("SliderPlugin", () => {
|
|
|
76
83
|
};
|
|
77
84
|
};
|
|
78
85
|
|
|
86
|
+
// When `steps` are provided, the slider works in *index* space: `value`,
|
|
87
|
+
// `start`, `stop` and `step` are all indices into the `steps` array, while
|
|
88
|
+
// the editable input shows/accepts the actual step values.
|
|
89
|
+
const createStepsProps = ({
|
|
90
|
+
steps,
|
|
91
|
+
valueIndex,
|
|
92
|
+
setValue,
|
|
93
|
+
}: {
|
|
94
|
+
steps: number[];
|
|
95
|
+
valueIndex: number;
|
|
96
|
+
setValue: ReturnType<typeof vi.fn>;
|
|
97
|
+
}): IPluginProps<
|
|
98
|
+
number,
|
|
99
|
+
z.infer<typeof SliderPlugin.prototype.validator>
|
|
100
|
+
> => {
|
|
101
|
+
return {
|
|
102
|
+
host: document.createElement("div"),
|
|
103
|
+
value: valueIndex,
|
|
104
|
+
setValue,
|
|
105
|
+
data: {
|
|
106
|
+
initialValue: valueIndex,
|
|
107
|
+
start: 0,
|
|
108
|
+
stop: steps.length - 1,
|
|
109
|
+
step: 1,
|
|
110
|
+
label: "Test Slider",
|
|
111
|
+
debounce: false,
|
|
112
|
+
orientation: "horizontal" as const,
|
|
113
|
+
showValue: false,
|
|
114
|
+
fullWidth: false,
|
|
115
|
+
includeInput: true,
|
|
116
|
+
steps,
|
|
117
|
+
},
|
|
118
|
+
functions: {},
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const typeAndCommit = (input: HTMLElement, value: string) => {
|
|
123
|
+
act(() => {
|
|
124
|
+
fireEvent.change(input, { target: { value } });
|
|
125
|
+
fireEvent.blur(input);
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
|
|
79
129
|
it("slider triggers setValue immediately when debounce is false", () => {
|
|
80
130
|
const plugin = new SliderPlugin();
|
|
81
131
|
const setValue = vi.fn();
|
|
82
|
-
const props = createProps(
|
|
132
|
+
const props = createProps({
|
|
133
|
+
debounce: false,
|
|
134
|
+
includeInput: false,
|
|
135
|
+
setValue,
|
|
136
|
+
});
|
|
83
137
|
const { getByRole } = render(plugin.render(props));
|
|
84
138
|
|
|
85
139
|
act(() => {
|
|
@@ -98,7 +152,11 @@ describe("SliderPlugin", () => {
|
|
|
98
152
|
it("slider waits until commit before calling setValue when debounce is true", () => {
|
|
99
153
|
const plugin = new SliderPlugin();
|
|
100
154
|
const setValue = vi.fn();
|
|
101
|
-
const props = createProps(
|
|
155
|
+
const props = createProps({
|
|
156
|
+
debounce: true,
|
|
157
|
+
includeInput: false,
|
|
158
|
+
setValue,
|
|
159
|
+
});
|
|
102
160
|
const { getByRole } = render(plugin.render(props));
|
|
103
161
|
|
|
104
162
|
act(() => {
|
|
@@ -124,7 +182,7 @@ describe("SliderPlugin", () => {
|
|
|
124
182
|
it("editable input triggers setValue immediately even when slider debounce is true", () => {
|
|
125
183
|
const plugin = new SliderPlugin();
|
|
126
184
|
const setValue = vi.fn();
|
|
127
|
-
const props = createProps(true, true, setValue);
|
|
185
|
+
const props = createProps({ debounce: true, includeInput: true, setValue });
|
|
128
186
|
const { getByRole } = render(plugin.render(props));
|
|
129
187
|
|
|
130
188
|
act(() => {
|
|
@@ -145,4 +203,216 @@ describe("SliderPlugin", () => {
|
|
|
145
203
|
// setValue should be called immediately regardless of debounce=true.
|
|
146
204
|
expect(setValue).toHaveBeenCalledWith(9);
|
|
147
205
|
});
|
|
206
|
+
|
|
207
|
+
describe("editable input with steps (regression for #9850)", () => {
|
|
208
|
+
it("displays the actual step value, not the index", () => {
|
|
209
|
+
const plugin = new SliderPlugin();
|
|
210
|
+
const setValue = vi.fn();
|
|
211
|
+
// steps[0] === -4, displayed value must be -4 (not the index 0).
|
|
212
|
+
const props = createStepsProps({
|
|
213
|
+
steps: [-4, -3, -2, -1],
|
|
214
|
+
valueIndex: 0,
|
|
215
|
+
setValue,
|
|
216
|
+
});
|
|
217
|
+
const { getByRole } = render(plugin.render(props));
|
|
218
|
+
|
|
219
|
+
act(() => {
|
|
220
|
+
vi.advanceTimersByTime(0);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const input = getByRole("textbox") as HTMLInputElement;
|
|
224
|
+
expect(input.value).toBe("-4");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("displays decimal step values without float artifacts", () => {
|
|
228
|
+
const plugin = new SliderPlugin();
|
|
229
|
+
const setValue = vi.fn();
|
|
230
|
+
const props = createStepsProps({
|
|
231
|
+
steps: [0.1, 0.2, 0.3, 0.4],
|
|
232
|
+
valueIndex: 2,
|
|
233
|
+
setValue,
|
|
234
|
+
});
|
|
235
|
+
const { getByRole } = render(plugin.render(props));
|
|
236
|
+
|
|
237
|
+
act(() => {
|
|
238
|
+
vi.advanceTimersByTime(0);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const input = getByRole("textbox") as HTMLInputElement;
|
|
242
|
+
expect(input.value).toBe("0.3");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("steps decimal input increment and decrement buttons move between steps", () => {
|
|
246
|
+
const plugin = new SliderPlugin();
|
|
247
|
+
const setValue = vi.fn();
|
|
248
|
+
const props = createStepsProps({
|
|
249
|
+
steps: [0.1, 0.2, 0.3, 0.4],
|
|
250
|
+
valueIndex: 2,
|
|
251
|
+
setValue,
|
|
252
|
+
});
|
|
253
|
+
const { getByRole } = render(plugin.render(props));
|
|
254
|
+
|
|
255
|
+
act(() => {
|
|
256
|
+
vi.advanceTimersByTime(0);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const decrement = getByRole("button", {
|
|
260
|
+
name: "Decrease Test Slider value input",
|
|
261
|
+
});
|
|
262
|
+
const increment = getByRole("button", {
|
|
263
|
+
name: "Increase Test Slider value input",
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(decrement).not.toBeDisabled();
|
|
267
|
+
|
|
268
|
+
act(() => {
|
|
269
|
+
fireEvent.click(decrement);
|
|
270
|
+
});
|
|
271
|
+
expect(setValue.mock.calls).toEqual([[1]]);
|
|
272
|
+
|
|
273
|
+
act(() => {
|
|
274
|
+
fireEvent.click(increment);
|
|
275
|
+
});
|
|
276
|
+
expect(setValue).toHaveBeenLastCalledWith(2);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("maps a typed integer step value back to its index", () => {
|
|
280
|
+
const plugin = new SliderPlugin();
|
|
281
|
+
const setValue = vi.fn();
|
|
282
|
+
const props = createStepsProps({
|
|
283
|
+
steps: [1, 2, 3, 4],
|
|
284
|
+
valueIndex: 0,
|
|
285
|
+
setValue,
|
|
286
|
+
});
|
|
287
|
+
const { getByRole } = render(plugin.render(props));
|
|
288
|
+
|
|
289
|
+
act(() => {
|
|
290
|
+
vi.advanceTimersByTime(0);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const input = getByRole("textbox");
|
|
294
|
+
// Typing "4" should select the last step (index 3), not index 4.
|
|
295
|
+
typeAndCommit(input, "4");
|
|
296
|
+
expect(setValue).toHaveBeenLastCalledWith(3);
|
|
297
|
+
|
|
298
|
+
// Typing "2" should select index 1, not get "stuck" on 3.
|
|
299
|
+
typeAndCommit(input, "2");
|
|
300
|
+
expect(setValue).toHaveBeenLastCalledWith(1);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("maps fractional step values back to their index", () => {
|
|
304
|
+
const plugin = new SliderPlugin();
|
|
305
|
+
const setValue = vi.fn();
|
|
306
|
+
const props = createStepsProps({
|
|
307
|
+
steps: [0.1, 0.2, 0.3, 0.4],
|
|
308
|
+
valueIndex: 0,
|
|
309
|
+
setValue,
|
|
310
|
+
});
|
|
311
|
+
const { getByRole } = render(plugin.render(props));
|
|
312
|
+
|
|
313
|
+
act(() => {
|
|
314
|
+
vi.advanceTimersByTime(0);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const input = getByRole("textbox");
|
|
318
|
+
typeAndCommit(input, "0.3");
|
|
319
|
+
expect(setValue).toHaveBeenLastCalledWith(2);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it("maps negative step values back to their index", () => {
|
|
323
|
+
const plugin = new SliderPlugin();
|
|
324
|
+
const setValue = vi.fn();
|
|
325
|
+
const props = createStepsProps({
|
|
326
|
+
steps: [-4, -3, -2, -1],
|
|
327
|
+
valueIndex: 0,
|
|
328
|
+
setValue,
|
|
329
|
+
});
|
|
330
|
+
const { getByRole } = render(plugin.render(props));
|
|
331
|
+
|
|
332
|
+
act(() => {
|
|
333
|
+
vi.advanceTimersByTime(0);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const input = getByRole("textbox");
|
|
337
|
+
typeAndCommit(input, "-2");
|
|
338
|
+
expect(setValue).toHaveBeenLastCalledWith(2);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("clamps out-of-range typed values to the nearest step index", () => {
|
|
342
|
+
const plugin = new SliderPlugin();
|
|
343
|
+
const setValue = vi.fn();
|
|
344
|
+
const props = createStepsProps({
|
|
345
|
+
steps: [1, 2, 3, 4],
|
|
346
|
+
valueIndex: 0,
|
|
347
|
+
setValue,
|
|
348
|
+
});
|
|
349
|
+
const { getByRole } = render(plugin.render(props));
|
|
350
|
+
|
|
351
|
+
act(() => {
|
|
352
|
+
vi.advanceTimersByTime(0);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const input = getByRole("textbox");
|
|
356
|
+
// Above the max step -> clamps to the last index.
|
|
357
|
+
typeAndCommit(input, "100");
|
|
358
|
+
expect(setValue).toHaveBeenLastCalledWith(3);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it("does not crash when steps shrink while the index is temporarily out of range", () => {
|
|
362
|
+
const plugin = new SliderPlugin();
|
|
363
|
+
const setValue = vi.fn();
|
|
364
|
+
// Start at the last index of a 5-element steps array.
|
|
365
|
+
const props = createStepsProps({
|
|
366
|
+
steps: [10, 20, 30, 40, 50],
|
|
367
|
+
valueIndex: 4,
|
|
368
|
+
setValue,
|
|
369
|
+
});
|
|
370
|
+
const { getByRole, rerender } = render(plugin.render(props));
|
|
371
|
+
|
|
372
|
+
act(() => {
|
|
373
|
+
vi.advanceTimersByTime(0);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// Shrink `steps` so the held index (4) is now out of range. The index
|
|
377
|
+
// syncs in an effect after this render, so the render must not throw on
|
|
378
|
+
// the stale, out-of-range index.
|
|
379
|
+
const shrunkProps = createStepsProps({
|
|
380
|
+
steps: [10, 20],
|
|
381
|
+
valueIndex: 1,
|
|
382
|
+
setValue,
|
|
383
|
+
});
|
|
384
|
+
expect(() => {
|
|
385
|
+
act(() => {
|
|
386
|
+
rerender(plugin.render(shrunkProps));
|
|
387
|
+
vi.advanceTimersByTime(0);
|
|
388
|
+
});
|
|
389
|
+
}).not.toThrow();
|
|
390
|
+
|
|
391
|
+
const input = getByRole("textbox") as HTMLInputElement;
|
|
392
|
+
expect(input.value).toBe("20");
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("nudges to the adjacent step on non-uniform steps even when the current step is nearest (known caveat)", () => {
|
|
396
|
+
const plugin = new SliderPlugin();
|
|
397
|
+
const setValue = vi.fn();
|
|
398
|
+
// Non-uniform steps; start at index 2 (value 10).
|
|
399
|
+
const props = createStepsProps({
|
|
400
|
+
steps: [0, 1, 10, 100],
|
|
401
|
+
valueIndex: 2,
|
|
402
|
+
setValue,
|
|
403
|
+
});
|
|
404
|
+
const { getByRole } = render(plugin.render(props));
|
|
405
|
+
|
|
406
|
+
act(() => {
|
|
407
|
+
vi.advanceTimersByTime(0);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const input = getByRole("textbox");
|
|
411
|
+
// 10 is far closer to 12 than 100 is, but because 12 > 10 the input nudges
|
|
412
|
+
// up one index. This documents the stepper-vs-typing limitation: onChange
|
|
413
|
+
// can't distinguish a typed value from a stepper-button increment.
|
|
414
|
+
typeAndCommit(input, "12");
|
|
415
|
+
expect(setValue).toHaveBeenLastCalledWith(3);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
148
418
|
});
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
import {
|
|
4
|
+
countFractionDigits,
|
|
5
|
+
maxFractionDigitsForSteps,
|
|
4
6
|
prettyEngineeringNumber,
|
|
5
7
|
prettyNumber,
|
|
6
8
|
prettyScientificNumber,
|
|
@@ -86,3 +88,21 @@ describe("prettyEngineeringNumber", () => {
|
|
|
86
88
|
expect(prettyEngineeringNumber(-0.000_123_4, locale)).toBe("-123µ");
|
|
87
89
|
});
|
|
88
90
|
});
|
|
91
|
+
|
|
92
|
+
describe("countFractionDigits", () => {
|
|
93
|
+
it("counts decimal places without float noise", () => {
|
|
94
|
+
expect(countFractionDigits(1)).toBe(0);
|
|
95
|
+
expect(countFractionDigits(0.1)).toBe(1);
|
|
96
|
+
expect(countFractionDigits(0.3)).toBe(1);
|
|
97
|
+
expect(countFractionDigits(0.000025)).toBe(6);
|
|
98
|
+
expect(countFractionDigits(3.5)).toBe(1);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("maxFractionDigitsForSteps", () => {
|
|
103
|
+
it("infers precision from steps and gaps", () => {
|
|
104
|
+
expect(maxFractionDigitsForSteps([0.1, 0.2, 0.3, 0.4], 0.1)).toBe(1);
|
|
105
|
+
expect(maxFractionDigitsForSteps([1, 2, 3.5, 4], 0.5)).toBe(1);
|
|
106
|
+
expect(maxFractionDigitsForSteps([1, 2, 3, 4], 1)).toBe(0);
|
|
107
|
+
});
|
|
108
|
+
});
|
package/src/utils/errors.ts
CHANGED
|
@@ -71,6 +71,15 @@ export class CellNotInitializedError extends Error {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
export class FunctionNotFoundError extends Error {
|
|
75
|
+
constructor(
|
|
76
|
+
message = "This UI element could not reach its function on the kernel. Try re-running the cell.",
|
|
77
|
+
) {
|
|
78
|
+
super(message);
|
|
79
|
+
this.name = "FunctionNotFoundError";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
74
83
|
export class NoKernelConnectedError extends Error {
|
|
75
84
|
constructor(message = "Not yet connected to a kernel.") {
|
|
76
85
|
super(message);
|
package/src/utils/lazy.ts
CHANGED
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
import React from "react";
|
|
4
4
|
|
|
5
5
|
interface LazyComponentWithPreload<T> {
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Eagerly trigger the dynamic import. Returns the import promise so callers
|
|
8
|
+
* can await it or attach error handling; safe to call multiple times (the
|
|
9
|
+
* import is memoized).
|
|
10
|
+
*/
|
|
11
|
+
preload: () => Promise<{ default: React.ComponentType<T> }>;
|
|
7
12
|
Component: React.LazyExoticComponent<React.ComponentType<T>>;
|
|
8
13
|
}
|
|
9
14
|
|
package/src/utils/numbers.ts
CHANGED
|
@@ -23,6 +23,33 @@ export const maxFractionalDigits = memoizeLastValue((locale: string) => {
|
|
|
23
23
|
return 0;
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
/** Decimal places needed to display `n` without float noise. */
|
|
27
|
+
export function countFractionDigits(n: number): number {
|
|
28
|
+
if (!Number.isFinite(n) || n === 0) {
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
const normalized = n.toFixed(12).replace(/\.?0+$/, "");
|
|
32
|
+
const dot = normalized.indexOf(".");
|
|
33
|
+
return dot === -1 ? 0 : normalized.length - dot - 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Max fraction digits to display a set of step values cleanly. */
|
|
37
|
+
export function maxFractionDigitsForSteps(
|
|
38
|
+
steps: number[],
|
|
39
|
+
minGap: number,
|
|
40
|
+
): number {
|
|
41
|
+
let max = countFractionDigits(minGap);
|
|
42
|
+
for (const step of steps) {
|
|
43
|
+
max = Math.max(max, countFractionDigits(step));
|
|
44
|
+
}
|
|
45
|
+
return max;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Round away float noise so step-based inputs stay on a clean decimal grid. */
|
|
49
|
+
export function roundToFractionDigits(n: number, digits: number): number {
|
|
50
|
+
return Number(n.toFixed(digits));
|
|
51
|
+
}
|
|
52
|
+
|
|
26
53
|
export function prettyNumber(value: unknown, locale: string): string {
|
|
27
54
|
if (value === undefined || value === null) {
|
|
28
55
|
return "";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./worker-ip3AI_sN.js";var t=e(((e,t)=>{t.exports={}}));export default t();
|