@marimo-team/islands 0.22.1-dev24 → 0.22.1-dev28
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/_basePickBy-Sow3pJjS.js +41 -0
- package/dist/{_baseUniq--7il0Js0.js → _baseUniq-C87CckHL.js} +14 -53
- package/dist/{architecture-7HQA4BMR-CSK94-xM.js → architecture-7HQA4BMR-BHdkAMvZ.js} +2 -2
- package/dist/{architectureDiagram-VXUJARFQ-Cw5_EP5P.js → architectureDiagram-VXUJARFQ-B3YQo9At.js} +9 -9
- package/dist/{blockDiagram-VD42YOAC-BiuOHEQv.js → blockDiagram-VD42YOAC-CpQ3TKEN.js} +2 -2
- package/dist/{chat-ui-MFxd7AGf.js → chat-ui-Wi1Lm6y4.js} +3 -3
- package/dist/{chunk-4F5CHEZ2-B0Jbisw2.js → chunk-4F5CHEZ2-D5mClyDv.js} +1 -1
- package/dist/{chunk-B2363JML-mJ3Q9WfR.js → chunk-B2363JML-Br0eA2T3.js} +1 -1
- package/dist/{chunk-B4BG7PRW-C4MiRcFI.js → chunk-B4BG7PRW-4BjV11Br.js} +1 -1
- package/dist/{chunk-DI55MBZ5-CMx27S12.js → chunk-DI55MBZ5-DITY3EyP.js} +1 -1
- package/dist/{chunk-FRFDVMJY-IPSTV3Ua.js → chunk-FRFDVMJY-DnEvEFRR.js} +1 -1
- package/dist/{chunk-N4CR4FBY-DYub3dan.js → chunk-N4CR4FBY-CpZSuGSU.js} +1 -1
- package/dist/{chunk-PL6DKKU2-DZiAP4HM.js → chunk-PL6DKKU2-DnId6G-x.js} +1 -1
- package/dist/{chunk-SJTYNZTY-s-hMTFeC.js → chunk-SJTYNZTY-BsBZnJUj.js} +1 -1
- package/dist/{chunk-TCCFYFTB-D7iQP6Bp.js → chunk-TCCFYFTB-Clbl-fTg.js} +5 -4
- package/dist/{chunk-TQ3KTPDO-BmFTqg51.js → chunk-TQ3KTPDO-CFkSQ30e.js} +1 -1
- package/dist/{chunk-UMXZTB3W-AUirFhbD.js → chunk-UMXZTB3W-D-A834Bq.js} +1 -1
- package/dist/{classDiagram-2ON5EDUG-BJCfK6hn.js → classDiagram-2ON5EDUG-C8-zE3Zv.js} +2 -2
- package/dist/{classDiagram-v2-WZHVMYZB-aaIrLBHp.js → classDiagram-v2-WZHVMYZB-DrmbGANl.js} +2 -2
- package/dist/{clone-BsAy1q8B.js → clone-DZFQCtFJ.js} +1 -1
- package/dist/{constants-CMDkKrpC.js → constants-CvyfaCvs.js} +1 -1
- package/dist/{dagre-6UL2VRFP-CLYtUWgC.js → dagre-6UL2VRFP-OMItEBnY.js} +5 -5
- package/dist/{dagre-oeMGMQA0.js → dagre-QVd-lCXU.js} +10 -20
- package/dist/{diagram-PSM6KHXK-D_6t-W86.js → diagram-PSM6KHXK-CkKbohWI.js} +9 -9
- package/dist/{diagram-QEK2KX5R-DyPwbpot.js → diagram-QEK2KX5R-DjUMpVcx.js} +9 -9
- package/dist/{diagram-S2PKOQOG-nwsLe-2u.js → diagram-S2PKOQOG-b-c0d-wZ.js} +9 -9
- package/dist/{erDiagram-Q2GNP2WA-C3PXcCCI.js → erDiagram-Q2GNP2WA-CDhLaOZ1.js} +1 -1
- package/dist/{flowDiagram-NV44I4VS-DFDCBPb3.js → flowDiagram-NV44I4VS-BDi4O4CL.js} +1 -1
- package/dist/{gitGraph-G5XIXVHT-CuhmzmB1.js → gitGraph-G5XIXVHT-B_c6xFJv.js} +2 -2
- package/dist/{gitGraphDiagram-V2S2FVAM-B3inBgCa.js → gitGraphDiagram-V2S2FVAM-iQnXzbPM.js} +9 -9
- package/dist/{glide-data-editor-kjT1twjd.js → glide-data-editor-D8O9AS1C.js} +2 -2
- package/dist/{graphlib-B05vp8B3.js → graphlib-BV1_gi0C.js} +2 -1
- package/dist/hasIn-DnfJcYpY.js +108 -0
- package/dist/{info-VBDWY6EO-CHQo46SB.js → info-VBDWY6EO-BTyzxmhr.js} +2 -2
- package/dist/{infoDiagram-HS3SLOUP-WHNM1sDJ.js → infoDiagram-HS3SLOUP-OYrX6uO3.js} +9 -9
- package/dist/{input-DONWC1s4.js → input-BeQSGpld.js} +1 -1
- package/dist/main.js +97 -75
- package/dist/{mermaid-BdEvqBXn.js → mermaid-808LPVim.js} +19 -19
- package/dist/{mermaid-parser.core-C2Dti_2f.js → mermaid-parser.core-ntCgyx0x.js} +8 -8
- package/dist/min-Ds3gG0Ff.js +96 -0
- package/dist/{mindmap-definition-VGOIOE7T-B02Y_l4r.js → mindmap-definition-VGOIOE7T-CxEUZZvY.js} +1 -1
- package/dist/{packet-DYOGHKS2-DjPkocnd.js → packet-DYOGHKS2-BhvnpoGi.js} +2 -2
- package/dist/{pie-VRWISCQL-B9pA8cOD.js → pie-VRWISCQL-dILuA3iG.js} +2 -2
- package/dist/{pieDiagram-ADFJNKIX-Bzhvjry9.js → pieDiagram-ADFJNKIX-U3LrUqAS.js} +9 -9
- package/dist/{process-output-LVENbROu.js → process-output-BvZAAk1w.js} +3 -3
- package/dist/{radar-ZZBFDIW7-C2obWVPx.js → radar-ZZBFDIW7-DwFrOJDj.js} +2 -2
- package/dist/range-fJeId9Ri.js +30 -0
- package/dist/{requirementDiagram-UZGBJVZJ-B7RuV-90.js → requirementDiagram-UZGBJVZJ-D0zpQnKC.js} +1 -1
- package/dist/{stateDiagram-FKZM4ZOC-QeUJrF7u.js → stateDiagram-FKZM4ZOC-B1S8jGMn.js} +4 -4
- package/dist/{stateDiagram-v2-4FDKWEC3-eOW_mDCq.js → stateDiagram-v2-4FDKWEC3-BH5ozUbc.js} +2 -2
- package/dist/{toDate-CamIA0ND.js → toDate-O4H9dZVC.js} +1 -1
- package/dist/{treemap-GDKQZRPO-DWMsd3D8.js → treemap-GDKQZRPO-bx2ngsgN.js} +2 -2
- package/dist/{types-IIG7e4M2.js → types-D_ntCXg0.js} +1 -1
- package/dist/{useDeepCompareMemoize-DAfQftmI.js → useDeepCompareMemoize-B8DwRVrX.js} +1 -1
- package/dist/{vega-component-CuWVhTG9.js → vega-component-C-fsM9rL.js} +3 -3
- package/package.json +4 -5
- package/src/__tests__/main.test.tsx +12 -14
- package/src/components/ai/ai-provider-icon.tsx +1 -2
- package/src/components/data-table/cell-selection/types.ts +3 -2
- package/src/components/data-table/charts/charts.tsx +2 -2
- package/src/components/data-table/column-formatting/types.ts +3 -2
- package/src/components/data-table/column-header.tsx +4 -2
- package/src/components/data-table/column-wrapping/types.ts +3 -2
- package/src/components/data-table/copy-column/types.ts +3 -2
- package/src/components/data-table/focus-row/types.ts +3 -2
- package/src/components/data-table/range-focus/__tests__/atoms.test.ts +11 -11
- package/src/components/data-table/range-focus/__tests__/use-cell-range-selection.test.ts +9 -11
- package/src/components/editor/__tests__/data-attributes.test.tsx +93 -94
- package/src/components/editor/actions/name-cell-input.tsx +4 -2
- package/src/components/editor/actions/useCellActionButton.tsx +4 -2
- package/src/components/editor/cell/CellStatus.tsx +4 -5
- package/src/components/editor/cell/cell-context-menu.tsx +4 -2
- package/src/components/editor/cell/code/cell-editor.tsx +2 -1
- package/src/components/editor/cell/toolbar.tsx +2 -1
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +11 -12
- package/src/components/storage/__tests__/storage-snippets.test.ts +4 -6
- package/src/components/tracing/tracing.test.tsx +30 -30
- package/src/components/ui/badge.tsx +2 -1
- package/src/components/ui/button.tsx +2 -1
- package/src/components/ui/calendar.tsx +3 -2
- package/src/components/ui/combobox.tsx +2 -1
- package/src/components/ui/date-input.tsx +7 -6
- package/src/components/ui/date-picker.tsx +6 -4
- package/src/components/ui/field.tsx +1 -2
- package/src/components/ui/progress.tsx +3 -2
- package/src/components/ui/query-param-preserving-link.tsx +4 -2
- package/src/components/ui/sheet.tsx +2 -1
- package/src/components/ui/textarea.tsx +1 -2
- package/src/core/ai/context/providers/cell-output.ts +1 -2
- package/src/core/ai/tools/edit-notebook-tool.ts +4 -3
- package/src/core/ai/tools/run-cells-tool.ts +4 -3
- package/src/core/cells/__tests__/add-missing-import.test.ts +23 -22
- package/src/core/cells/__tests__/cell.test.ts +14 -13
- package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -17
- package/src/core/codemirror/language/languages/markdown.ts +1 -3
- package/src/core/codemirror/language/languages/python.ts +1 -1
- package/src/core/codemirror/language/languages/sql/completion-sources.tsx +4 -6
- package/src/core/codemirror/language/languages/sql/sql.ts +1 -3
- package/src/core/codemirror/reactive-references/__tests__/analyzer.test.ts +28 -42
- package/src/core/datasets/data-source-connections.ts +4 -2
- package/src/core/dom/__tests__/htmlUtils.test.ts +8 -14
- package/src/core/dom/__tests__/outline.test.ts +2 -3
- package/src/core/islands/__tests__/parse.test.ts +8 -7
- package/src/core/saving/__tests__/filename.test.ts +7 -6
- package/src/core/static/__tests__/download-html.test.ts +16 -15
- package/src/core/static/__tests__/files.test.ts +30 -28
- package/src/css/app/Cell.css +5 -2
- package/src/css/globals.css +40 -14
- package/src/plugins/impl/DataEditorPlugin.tsx +4 -2
- package/src/plugins/impl/FormPlugin.tsx +1 -2
- package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +43 -15
- package/src/plugins/impl/data-frames/forms/__tests__/form.test.tsx +7 -9
- package/src/plugins/impl/plotly/PlotlyPlugin.tsx +12 -68
- package/src/plugins/impl/plotly/__tests__/selection.test.ts +237 -0
- package/src/plugins/impl/plotly/selection.ts +118 -0
- package/src/plugins/impl/vega/__tests__/make-selectable.test.ts +13 -14
- package/src/plugins/layout/ImageComparisonPlugin.tsx +1 -3
- package/src/plugins/stateless-plugin.ts +4 -2
- package/src/utils/__tests__/cell-urls.test.ts +24 -21
- package/src/utils/__tests__/filenames.test.ts +15 -14
- package/src/utils/__tests__/json-parser.test.ts +14 -21
- package/src/utils/__tests__/path.test.ts +34 -31
- package/src/utils/__tests__/urls.test.ts +19 -18
- package/dist/_basePickBy-CsPbmRlg.js +0 -110
- package/dist/_baseSet-Cs7YTRXk.js +0 -27
- package/dist/get-Wu1vTRhN.js +0 -68
- package/dist/range-CNXr10o1.js +0 -17
- /package/dist/{now-Dh_IQA36.js → now-nrrrOr01.js} +0 -0
|
@@ -192,20 +192,21 @@ describe("patchVegaLoader - loader.http", () => {
|
|
|
192
192
|
"http://foo.com/virtual-file.json",
|
|
193
193
|
];
|
|
194
194
|
|
|
195
|
-
it.each(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
195
|
+
it.each(pathsToTest)(
|
|
196
|
+
"should return file content for virtual files for %s",
|
|
197
|
+
async (s) => {
|
|
198
|
+
const virtualFiles = {
|
|
199
|
+
"/virtual-file.json":
|
|
200
|
+
"data:application/json;base64,eyJrZXkiOiAidmFsdWUifQ==" as DataURLString,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const loader = createLoader();
|
|
204
|
+
const unpatch = patchVegaLoader(loader, virtualFiles);
|
|
205
|
+
const content = await loader.http(s);
|
|
206
|
+
unpatch();
|
|
207
|
+
expect(content).toBe('{"key": "value"}');
|
|
208
|
+
},
|
|
209
|
+
);
|
|
209
210
|
|
|
210
211
|
it("should fallback to original http method for non-virtual files", async () => {
|
|
211
212
|
const loader = createLoader();
|
|
@@ -236,20 +237,21 @@ describe("patchVegaLoader - loader.load", () => {
|
|
|
236
237
|
"http://foo.com/virtual-file.json",
|
|
237
238
|
];
|
|
238
239
|
|
|
239
|
-
it.each(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
240
|
+
it.each(pathsToTest)(
|
|
241
|
+
"should return file content for virtual files for %s",
|
|
242
|
+
async (s) => {
|
|
243
|
+
const virtualFiles = {
|
|
244
|
+
"/virtual-file.json":
|
|
245
|
+
"data:application/json;base64,eyJrZXkiOiAidmFsdWUifQ==" as DataURLString,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const loader = createLoader();
|
|
249
|
+
const unpatch = patchVegaLoader(loader, virtualFiles);
|
|
250
|
+
const content = await loader.load(s);
|
|
251
|
+
unpatch();
|
|
252
|
+
expect(content).toBe('{"key": "value"}');
|
|
253
|
+
},
|
|
254
|
+
);
|
|
253
255
|
|
|
254
256
|
it("should fallback to original load method for non-virtual files", async () => {
|
|
255
257
|
const loader = createLoader();
|
package/src/css/app/Cell.css
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
border: 1px solid var(--gray-4);
|
|
16
16
|
|
|
17
17
|
@apply divide-y divide-(--gray-5);
|
|
18
|
+
|
|
18
19
|
&:hover {
|
|
19
20
|
border-color: var(--gray-6);
|
|
20
21
|
}
|
|
@@ -99,8 +100,8 @@
|
|
|
99
100
|
/* Special case for particular components */
|
|
100
101
|
|
|
101
102
|
.output-area:has(
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
> .output:only-child > marimo-ui-element:only-child > marimo-table
|
|
104
|
+
) {
|
|
104
105
|
padding: 0 0 5px;
|
|
105
106
|
max-height: none;
|
|
106
107
|
overflow: hidden;
|
|
@@ -112,6 +113,7 @@
|
|
|
112
113
|
margin-top: 0.25rem;
|
|
113
114
|
border: none;
|
|
114
115
|
border-radius: 0;
|
|
116
|
+
border-top-left-radius: 9px;
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
marimo-table::part(table-wrapper) {
|
|
@@ -385,6 +387,7 @@
|
|
|
385
387
|
border-top-left-radius: 9px;
|
|
386
388
|
border-top-right-radius: 9px;
|
|
387
389
|
}
|
|
390
|
+
|
|
388
391
|
div[data-ai-input-open="true"] .cm-editor {
|
|
389
392
|
border-top-left-radius: 0;
|
|
390
393
|
border-top-right-radius: 0;
|
package/src/css/globals.css
CHANGED
|
@@ -148,46 +148,72 @@
|
|
|
148
148
|
--shadow-xxs: 0px 0px 2px 0px var(--base-shadow-darker);
|
|
149
149
|
|
|
150
150
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
151
|
-
--shadow-xs:
|
|
151
|
+
--shadow-xs:
|
|
152
|
+
1px 1px 2px 0px var(--base-shadow),
|
|
153
|
+
0px 0px 2px 0px hsl(0deg 0% 25% / var(--base-shadow-opacity));
|
|
152
154
|
|
|
153
155
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
154
|
-
--shadow-sm:
|
|
156
|
+
--shadow-sm:
|
|
157
|
+
2px 2px 2px 0px var(--base-shadow),
|
|
158
|
+
0px 0px 2px 0px hsl(0deg 0% 25% / var(--base-shadow-opacity));
|
|
155
159
|
|
|
156
160
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
157
|
-
--shadow-md:
|
|
161
|
+
--shadow-md:
|
|
162
|
+
4px 4px 4px 0px var(--base-shadow),
|
|
163
|
+
0 0px 4px 0px hsl(0deg 0% 60% / var(--base-shadow-opacity));
|
|
158
164
|
|
|
159
165
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
160
|
-
--shadow-lg:
|
|
166
|
+
--shadow-lg:
|
|
167
|
+
5px 6px 4px 0px var(--base-shadow),
|
|
168
|
+
0 0px 4px 0px hsl(0deg 0% 75% / var(--base-shadow-opacity));
|
|
161
169
|
|
|
162
170
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
163
|
-
--shadow-xl:
|
|
171
|
+
--shadow-xl:
|
|
172
|
+
8px 9px 4px 0px var(--base-shadow),
|
|
173
|
+
0 0px 6px 0px hsl(0deg 0% 85% / var(--base-shadow-opacity));
|
|
164
174
|
|
|
165
175
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
166
|
-
--shadow-2xl:
|
|
176
|
+
--shadow-2xl:
|
|
177
|
+
10px 12px 10px 0px var(--base-shadow),
|
|
178
|
+
0 0px 8px 0px hsl(0deg 0% 90% / var(--base-shadow-opacity));
|
|
167
179
|
|
|
168
180
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
169
|
-
--shadow-xs-solid:
|
|
181
|
+
--shadow-xs-solid:
|
|
182
|
+
1px 1px 0px 0px var(--base-shadow-darker),
|
|
183
|
+
0px 0px 2px 0px hsl(0deg 0% 50% / 20%);
|
|
170
184
|
|
|
171
185
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
172
|
-
--shadow-sm-solid:
|
|
186
|
+
--shadow-sm-solid:
|
|
187
|
+
2px 2px 0px 0px var(--base-shadow-darker),
|
|
188
|
+
0px 0px 2px 0px hsl(0deg 0% 50% / 20%);
|
|
173
189
|
|
|
174
190
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
175
|
-
--shadow-md-solid:
|
|
191
|
+
--shadow-md-solid:
|
|
192
|
+
4px 4px 0px 0px var(--base-shadow-darker),
|
|
193
|
+
0 0px 2px 0px hsl(0deg 0% 60% / 50%);
|
|
176
194
|
|
|
177
195
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
178
|
-
--shadow-lg-solid:
|
|
196
|
+
--shadow-lg-solid:
|
|
197
|
+
5px 6px 0px 0px var(--base-shadow-darker),
|
|
198
|
+
0 0px 4px 0px hsl(0deg 0% 75% / 50%);
|
|
179
199
|
|
|
180
200
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
181
|
-
--shadow-xl-solid:
|
|
201
|
+
--shadow-xl-solid:
|
|
202
|
+
7px 8px 0px 0px var(--base-shadow-darker),
|
|
203
|
+
0 0px 4px 0px hsl(0deg 0% 85% / 50%);
|
|
182
204
|
|
|
183
205
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
184
|
-
--shadow-2xl-solid:
|
|
206
|
+
--shadow-2xl-solid:
|
|
207
|
+
10px 12px 0px 0px var(--base-shadow-darker),
|
|
208
|
+
0 0px 8px 0px hsl(0deg 0% 90% / 50%);
|
|
185
209
|
|
|
186
210
|
/* Solid shadows with lighter shade color */
|
|
187
211
|
|
|
188
212
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
189
|
-
--shadow-sm-solid-shade:
|
|
213
|
+
--shadow-sm-solid-shade:
|
|
214
|
+
2px 2px 0px 0px var(--base-shadow), 0px 0px 2px 0px hsl(0deg 0% 50% / 20%);
|
|
190
215
|
|
|
191
216
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
192
|
-
--shadow-md-solid-shade:
|
|
217
|
+
--shadow-md-solid-shade:
|
|
218
|
+
4px 4px 0px 0px var(--base-shadow), 0 0px 2px 0px hsl(0deg 0% 60% / 50%);
|
|
193
219
|
}
|
|
@@ -76,8 +76,10 @@ export const DataEditorPlugin = createPlugin<Edits>("marimo-data-editor", {
|
|
|
76
76
|
);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
interface Props
|
|
80
|
-
|
|
79
|
+
interface Props extends Omit<
|
|
80
|
+
DataEditorProps<object>,
|
|
81
|
+
"data" | "onAddEdits" | "onAddRows"
|
|
82
|
+
> {
|
|
81
83
|
data: TableData<object>;
|
|
82
84
|
edits: Edits;
|
|
83
85
|
onEdits: Setter<Edits>;
|
|
@@ -80,8 +80,7 @@ export const FormPlugin = createPlugin("marimo-form")
|
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
export interface FormWrapperProps<T>
|
|
83
|
-
extends Omit<Data, "elementId">,
|
|
84
|
-
Functions {
|
|
83
|
+
extends Omit<Data, "elementId">, Functions {
|
|
85
84
|
children: React.ReactNode;
|
|
86
85
|
currentValue: T;
|
|
87
86
|
newValue: T;
|
|
@@ -9,6 +9,35 @@ import { store } from "@/core/state/jotai";
|
|
|
9
9
|
import type { IPluginProps } from "../../types";
|
|
10
10
|
import { SliderPlugin } from "../SliderPlugin";
|
|
11
11
|
|
|
12
|
+
vi.mock("@/components/ui/slider", () => ({
|
|
13
|
+
Slider: ({
|
|
14
|
+
disabled,
|
|
15
|
+
onValueChange,
|
|
16
|
+
onValueCommit,
|
|
17
|
+
value,
|
|
18
|
+
}: {
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
onValueChange?: (value: number[]) => void;
|
|
21
|
+
onValueCommit?: (value: number[]) => void;
|
|
22
|
+
value: number[];
|
|
23
|
+
}) => (
|
|
24
|
+
<div>
|
|
25
|
+
<button
|
|
26
|
+
aria-label="Slider change"
|
|
27
|
+
disabled={disabled}
|
|
28
|
+
onClick={() => onValueChange?.([value[0] + 1])}
|
|
29
|
+
type="button"
|
|
30
|
+
/>
|
|
31
|
+
<button
|
|
32
|
+
aria-label="Slider commit"
|
|
33
|
+
disabled={disabled}
|
|
34
|
+
onClick={() => onValueCommit?.(value)}
|
|
35
|
+
type="button"
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
),
|
|
39
|
+
}));
|
|
40
|
+
|
|
12
41
|
SetupMocks.resizeObserver();
|
|
13
42
|
|
|
14
43
|
describe("SliderPlugin", () => {
|
|
@@ -51,46 +80,45 @@ describe("SliderPlugin", () => {
|
|
|
51
80
|
const plugin = new SliderPlugin();
|
|
52
81
|
const setValue = vi.fn();
|
|
53
82
|
const props = createProps(false, false, setValue);
|
|
54
|
-
const {
|
|
83
|
+
const { getByRole } = render(plugin.render(props));
|
|
55
84
|
|
|
56
85
|
act(() => {
|
|
57
86
|
vi.advanceTimersByTime(0);
|
|
58
87
|
});
|
|
59
88
|
|
|
60
|
-
const
|
|
61
|
-
expect(thumb).toBeTruthy();
|
|
89
|
+
const changeButton = getByRole("button", { name: "Slider change" });
|
|
62
90
|
|
|
63
|
-
// Radix UI Slider updates on keyboard ArrowRight/ArrowLeft
|
|
64
91
|
act(() => {
|
|
65
|
-
(
|
|
66
|
-
fireEvent.keyDown(thumb!, { key: "ArrowRight" });
|
|
92
|
+
fireEvent.click(changeButton);
|
|
67
93
|
});
|
|
68
94
|
|
|
69
95
|
expect(setValue).toHaveBeenCalledWith(6);
|
|
70
96
|
});
|
|
71
97
|
|
|
72
|
-
it("slider
|
|
98
|
+
it("slider waits until commit before calling setValue when debounce is true", () => {
|
|
73
99
|
const plugin = new SliderPlugin();
|
|
74
100
|
const setValue = vi.fn();
|
|
75
101
|
const props = createProps(true, false, setValue);
|
|
76
|
-
const {
|
|
102
|
+
const { getByRole } = render(plugin.render(props));
|
|
77
103
|
|
|
78
104
|
act(() => {
|
|
79
105
|
vi.advanceTimersByTime(0);
|
|
80
106
|
});
|
|
81
107
|
|
|
82
|
-
const
|
|
108
|
+
const changeButton = getByRole("button", { name: "Slider change" });
|
|
109
|
+
const commitButton = getByRole("button", { name: "Slider commit" });
|
|
83
110
|
|
|
84
111
|
act(() => {
|
|
85
|
-
(
|
|
86
|
-
// Simulate just a programmatic change that Radix would trigger via pointer move
|
|
87
|
-
// which fires onValueChange but not onValueCommit yet
|
|
88
|
-
// Because we can't easily separated Radix's internal pointer events in jsdom, we
|
|
89
|
-
// test the main issue: editable input. We can trust Radix's onValueChange vs onValueCommit.
|
|
112
|
+
fireEvent.click(changeButton);
|
|
90
113
|
});
|
|
91
114
|
|
|
92
|
-
// We verified above that NumberField works when debounce=true
|
|
93
115
|
expect(setValue).not.toHaveBeenCalled();
|
|
116
|
+
|
|
117
|
+
act(() => {
|
|
118
|
+
fireEvent.click(commitButton);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(setValue).toHaveBeenCalledWith(6);
|
|
94
122
|
});
|
|
95
123
|
|
|
96
124
|
it("editable input triggers setValue immediately even when slider debounce is true", () => {
|
|
@@ -44,16 +44,14 @@ describe("renderZodSchema", () => {
|
|
|
44
44
|
[...TransformTypeSchema.options],
|
|
45
45
|
(z) => getUnionLiteral(z).value,
|
|
46
46
|
);
|
|
47
|
-
it.each(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
FieldValues
|
|
52
|
-
>) => {
|
|
53
|
-
const expected = render(<Subject schema={schema} />);
|
|
47
|
+
it.each(Object.entries(options))(
|
|
48
|
+
"should render a form %s",
|
|
49
|
+
(name, schema: z.ZodType<unknown, FieldValues>) => {
|
|
50
|
+
const expected = render(<Subject schema={schema} />);
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
expect(expected.asFragment()).toMatchSnapshot();
|
|
53
|
+
},
|
|
54
|
+
);
|
|
57
55
|
});
|
|
58
56
|
|
|
59
57
|
const options = [
|
|
@@ -15,7 +15,11 @@ import { useDeepCompareMemoize } from "@/hooks/useDeepCompareMemoize";
|
|
|
15
15
|
import { useScript } from "@/hooks/useScript";
|
|
16
16
|
import { Arrays } from "@/utils/arrays";
|
|
17
17
|
import { Objects } from "@/utils/objects";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
extractClickSelection,
|
|
20
|
+
extractIndices,
|
|
21
|
+
extractPoints,
|
|
22
|
+
} from "./selection";
|
|
19
23
|
import { usePlotlyLayout } from "./usePlotlyLayout";
|
|
20
24
|
|
|
21
25
|
interface Data {
|
|
@@ -23,12 +27,9 @@ interface Data {
|
|
|
23
27
|
config: Partial<Plotly.Config>;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
type AxisName = string;
|
|
27
|
-
type AxisDatum = unknown;
|
|
28
|
-
|
|
29
30
|
type T =
|
|
30
31
|
| {
|
|
31
|
-
points?: Record<
|
|
32
|
+
points?: Record<string, unknown>[] | Plotly.PlotDatum[];
|
|
32
33
|
indices?: number[];
|
|
33
34
|
range?: {
|
|
34
35
|
x?: number[];
|
|
@@ -202,19 +203,13 @@ export const PlotlyComponent = memo(
|
|
|
202
203
|
if (!evt) {
|
|
203
204
|
return;
|
|
204
205
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
(point) => point.data?.type === "heatmap",
|
|
209
|
-
);
|
|
210
|
-
if (!isHeatmap) {
|
|
206
|
+
|
|
207
|
+
const clickSelection = extractClickSelection(evt);
|
|
208
|
+
if (!clickSelection) {
|
|
211
209
|
return;
|
|
212
210
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
points: extractPoints(evt.points),
|
|
216
|
-
indices: evt.points.map((point) => point.pointIndex),
|
|
217
|
-
}));
|
|
211
|
+
|
|
212
|
+
setValue((prev) => ({ ...prev, ...clickSelection }));
|
|
218
213
|
})}
|
|
219
214
|
onSelected={useEvent((evt: Readonly<Plotly.PlotSelectionEvent>) => {
|
|
220
215
|
if (!evt) {
|
|
@@ -226,7 +221,7 @@ export const PlotlyComponent = memo(
|
|
|
226
221
|
selections:
|
|
227
222
|
"selections" in evt ? (evt.selections as unknown[]) : [],
|
|
228
223
|
points: extractPoints(evt.points),
|
|
229
|
-
indices: evt.points
|
|
224
|
+
indices: extractIndices(evt.points),
|
|
230
225
|
range: evt.range,
|
|
231
226
|
}));
|
|
232
227
|
})}
|
|
@@ -241,54 +236,3 @@ export const PlotlyComponent = memo(
|
|
|
241
236
|
},
|
|
242
237
|
);
|
|
243
238
|
PlotlyComponent.displayName = "PlotlyComponent";
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* This is a hack to extract the points with their original keys,
|
|
247
|
-
* instead of the ones that Plotly uses internally,
|
|
248
|
-
* by using the hovertemplate.
|
|
249
|
-
*/
|
|
250
|
-
const STANDARD_POINT_KEYS: string[] = [
|
|
251
|
-
"x",
|
|
252
|
-
"y",
|
|
253
|
-
"z",
|
|
254
|
-
"lat",
|
|
255
|
-
"lon",
|
|
256
|
-
"curveNumber",
|
|
257
|
-
"pointNumber",
|
|
258
|
-
"pointNumbers",
|
|
259
|
-
"pointIndex",
|
|
260
|
-
];
|
|
261
|
-
|
|
262
|
-
function extractPoints(
|
|
263
|
-
points: Plotly.PlotDatum[],
|
|
264
|
-
): Record<AxisName, AxisDatum>[] {
|
|
265
|
-
if (!points) {
|
|
266
|
-
return [];
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
let parser: PlotlyTemplateParser | undefined;
|
|
270
|
-
|
|
271
|
-
return points.map((point) => {
|
|
272
|
-
const standardPointFields = Objects.pick(point, STANDARD_POINT_KEYS);
|
|
273
|
-
|
|
274
|
-
// Get the first hovertemplate
|
|
275
|
-
const hovertemplate = Array.isArray(point.data.hovertemplate)
|
|
276
|
-
? point.data.hovertemplate[0]
|
|
277
|
-
: point.data.hovertemplate;
|
|
278
|
-
|
|
279
|
-
// For chart types with standard point keys (e.g. heatmaps),
|
|
280
|
-
// or when there's no hovertemplate, pick keys directly from the point.
|
|
281
|
-
if (!hovertemplate || point.data?.type === "heatmap") {
|
|
282
|
-
return standardPointFields;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Update or create a parser
|
|
286
|
-
parser = parser
|
|
287
|
-
? parser.update(hovertemplate)
|
|
288
|
-
: createParser(hovertemplate);
|
|
289
|
-
return {
|
|
290
|
-
...standardPointFields,
|
|
291
|
-
...parser.parse(point),
|
|
292
|
-
};
|
|
293
|
-
});
|
|
294
|
-
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type * as Plotly from "plotly.js";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import {
|
|
6
|
+
extractClickSelection,
|
|
7
|
+
extractIndices,
|
|
8
|
+
extractPoints,
|
|
9
|
+
} from "../selection";
|
|
10
|
+
|
|
11
|
+
interface PlotlyPointInput {
|
|
12
|
+
data: {
|
|
13
|
+
type: string;
|
|
14
|
+
hovertemplate?: string | string[];
|
|
15
|
+
};
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function makePoint(point: PlotlyPointInput): Plotly.PlotDatum {
|
|
20
|
+
return point as unknown as Plotly.PlotDatum;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function makeClickEvent(
|
|
24
|
+
points: Plotly.PlotDatum[],
|
|
25
|
+
): Readonly<Plotly.PlotMouseEvent> {
|
|
26
|
+
return { points } as unknown as Readonly<Plotly.PlotMouseEvent>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("extractIndices", () => {
|
|
30
|
+
it("prefers pointIndex and falls back to pointNumber", () => {
|
|
31
|
+
const points = [
|
|
32
|
+
makePoint({
|
|
33
|
+
pointIndex: 2,
|
|
34
|
+
pointNumber: 99,
|
|
35
|
+
data: { type: "scatter" },
|
|
36
|
+
}),
|
|
37
|
+
makePoint({
|
|
38
|
+
pointNumber: 4,
|
|
39
|
+
data: { type: "scattergl" },
|
|
40
|
+
}),
|
|
41
|
+
makePoint({
|
|
42
|
+
data: { type: "heatmap" },
|
|
43
|
+
}),
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
expect(extractIndices(points)).toEqual([2, 4]);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("extractPoints", () => {
|
|
51
|
+
it("extracts parsed scatter payload fields from the hovertemplate", () => {
|
|
52
|
+
const points = [
|
|
53
|
+
makePoint({
|
|
54
|
+
x: 3,
|
|
55
|
+
y: 7,
|
|
56
|
+
curveNumber: 0,
|
|
57
|
+
pointIndex: 1,
|
|
58
|
+
customdata: ["B"],
|
|
59
|
+
data: {
|
|
60
|
+
type: "scatter",
|
|
61
|
+
hovertemplate:
|
|
62
|
+
"label=%{customdata[0]}<br>x=%{x}<br>y=%{y}<extra></extra>",
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
expect(extractPoints(points)).toEqual([
|
|
68
|
+
{
|
|
69
|
+
x: 3,
|
|
70
|
+
y: 7,
|
|
71
|
+
curveNumber: 0,
|
|
72
|
+
pointIndex: 1,
|
|
73
|
+
label: "B",
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("keeps standard heatmap keys without hovertemplate parsing", () => {
|
|
79
|
+
const points = [
|
|
80
|
+
makePoint({
|
|
81
|
+
x: "B",
|
|
82
|
+
y: "Row 2",
|
|
83
|
+
z: 6,
|
|
84
|
+
curveNumber: 0,
|
|
85
|
+
pointIndex: 5,
|
|
86
|
+
data: { type: "heatmap", hovertemplate: "ignored=%{z}" },
|
|
87
|
+
}),
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
expect(extractPoints(points)).toEqual([
|
|
91
|
+
{
|
|
92
|
+
x: "B",
|
|
93
|
+
y: "Row 2",
|
|
94
|
+
z: 6,
|
|
95
|
+
curveNumber: 0,
|
|
96
|
+
pointIndex: 5,
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("extractClickSelection", () => {
|
|
103
|
+
it("returns undefined for unsupported trace types", () => {
|
|
104
|
+
const event = makeClickEvent([
|
|
105
|
+
makePoint({
|
|
106
|
+
x: "A",
|
|
107
|
+
y: 10,
|
|
108
|
+
pointIndex: 0,
|
|
109
|
+
data: { type: "bar" },
|
|
110
|
+
}),
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
expect(extractClickSelection(event)).toBeUndefined();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("returns undefined when all points are non-click-selectable trace types", () => {
|
|
117
|
+
// scatter and scattergl use onSelected (box/lasso) for selection, not onClick.
|
|
118
|
+
// Clicks on these traces fire both plotly_click and plotly_selected; the
|
|
119
|
+
// latter provides a range and must be the authoritative source.
|
|
120
|
+
const event = makeClickEvent([
|
|
121
|
+
makePoint({
|
|
122
|
+
x: "ignore",
|
|
123
|
+
y: 1,
|
|
124
|
+
pointIndex: 0,
|
|
125
|
+
data: { type: "bar" },
|
|
126
|
+
}),
|
|
127
|
+
makePoint({
|
|
128
|
+
x: 2,
|
|
129
|
+
y: 5,
|
|
130
|
+
curveNumber: 1,
|
|
131
|
+
pointIndex: 3,
|
|
132
|
+
data: { type: "scatter" },
|
|
133
|
+
}),
|
|
134
|
+
makePoint({
|
|
135
|
+
x: 4,
|
|
136
|
+
y: 12,
|
|
137
|
+
curveNumber: 2,
|
|
138
|
+
pointNumber: 5,
|
|
139
|
+
data: { type: "scattergl" },
|
|
140
|
+
}),
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
expect(extractClickSelection(event)).toBeUndefined();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("filters unsupported points and preserves supported click payloads", () => {
|
|
147
|
+
// bar is unsupported; histogram is supported and its pointNumbers must be
|
|
148
|
+
// forwarded so the backend can recover the exact sample rows.
|
|
149
|
+
const event = makeClickEvent([
|
|
150
|
+
makePoint({
|
|
151
|
+
x: "ignore",
|
|
152
|
+
y: 1,
|
|
153
|
+
pointIndex: 0,
|
|
154
|
+
data: { type: "bar" },
|
|
155
|
+
}),
|
|
156
|
+
makePoint({
|
|
157
|
+
x: 8,
|
|
158
|
+
y: 3,
|
|
159
|
+
curveNumber: 1,
|
|
160
|
+
pointNumber: 2,
|
|
161
|
+
pointNumbers: [4, 5, 6],
|
|
162
|
+
data: { type: "histogram" },
|
|
163
|
+
}),
|
|
164
|
+
]);
|
|
165
|
+
|
|
166
|
+
expect(extractClickSelection(event)).toEqual({
|
|
167
|
+
selections: [],
|
|
168
|
+
range: undefined,
|
|
169
|
+
indices: [2],
|
|
170
|
+
points: [
|
|
171
|
+
{
|
|
172
|
+
x: 8,
|
|
173
|
+
y: 3,
|
|
174
|
+
curveNumber: 1,
|
|
175
|
+
pointNumber: 2,
|
|
176
|
+
pointNumbers: [4, 5, 6],
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("preserves histogram pointNumbers for backend row extraction", () => {
|
|
183
|
+
const event = makeClickEvent([
|
|
184
|
+
makePoint({
|
|
185
|
+
x: 8,
|
|
186
|
+
y: 2,
|
|
187
|
+
curveNumber: 0,
|
|
188
|
+
pointNumber: 3,
|
|
189
|
+
pointNumbers: [6, 7],
|
|
190
|
+
data: { type: "histogram" },
|
|
191
|
+
}),
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
expect(extractClickSelection(event)).toEqual({
|
|
195
|
+
selections: [],
|
|
196
|
+
range: undefined,
|
|
197
|
+
indices: [3],
|
|
198
|
+
points: [
|
|
199
|
+
{
|
|
200
|
+
x: 8,
|
|
201
|
+
y: 2,
|
|
202
|
+
curveNumber: 0,
|
|
203
|
+
pointNumber: 3,
|
|
204
|
+
pointNumbers: [6, 7],
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("preserves standard heatmap click payloads", () => {
|
|
211
|
+
const event = makeClickEvent([
|
|
212
|
+
makePoint({
|
|
213
|
+
x: "C",
|
|
214
|
+
y: "Row 3",
|
|
215
|
+
z: 11,
|
|
216
|
+
curveNumber: 0,
|
|
217
|
+
pointIndex: 10,
|
|
218
|
+
data: { type: "heatmap" },
|
|
219
|
+
}),
|
|
220
|
+
]);
|
|
221
|
+
|
|
222
|
+
expect(extractClickSelection(event)).toEqual({
|
|
223
|
+
selections: [],
|
|
224
|
+
range: undefined,
|
|
225
|
+
indices: [10],
|
|
226
|
+
points: [
|
|
227
|
+
{
|
|
228
|
+
x: "C",
|
|
229
|
+
y: "Row 3",
|
|
230
|
+
z: 11,
|
|
231
|
+
curveNumber: 0,
|
|
232
|
+
pointIndex: 10,
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
});
|