@marimo-team/frontend 0.22.1-dev33 → 0.22.1-dev38

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 (41) hide show
  1. package/dist/assets/{JsonOutput-CNt4LxkV.js → JsonOutput-DboWEw2n.js} +2 -2
  2. package/dist/assets/{add-connection-dialog-Ca1Tc12W.js → add-connection-dialog-DiHC8_uD.js} +1 -1
  3. package/dist/assets/{agent-panel-BYAJ_EOf.js → agent-panel-CWWFNdAZ.js} +1 -1
  4. package/dist/assets/{cell-editor-CSxVMYfU.js → cell-editor-CKnV9MwH.js} +2 -2
  5. package/dist/assets/{column-preview-B92BXVH1.js → column-preview-BvDPfcdF.js} +1 -1
  6. package/dist/assets/{command-palette-BHXQKZ5s.js → command-palette-CPRmmNjA.js} +1 -1
  7. package/dist/assets/common-CRBlPqv5.js +1 -0
  8. package/dist/assets/{dependency-graph-panel-CABb99dr.js → dependency-graph-panel-BBmN-Vc7.js} +1 -1
  9. package/dist/assets/{edit-page-DtYaxOp-.js → edit-page-B-kCXJQD.js} +4 -4
  10. package/dist/assets/{file-explorer-panel-DJgn_eST.js → file-explorer-panel-DyDct4o3.js} +1 -1
  11. package/dist/assets/{form-BFjzbGu5.js → form-Byef3KYr.js} +1 -1
  12. package/dist/assets/{hooks-C2LN7xdC.js → hooks-BoxLBXGI.js} +1 -1
  13. package/dist/assets/index-B1HqiNNr.js +42 -0
  14. package/dist/assets/{index-BaQAJwyb.css → index-BkdonYlq.css} +1 -1
  15. package/dist/assets/{layout-Cb75LpRE.js → layout-BjQtqcnj.js} +2 -2
  16. package/dist/assets/{panels-C-PHvQEf.js → panels-aBXT7_tR.js} +1 -1
  17. package/dist/assets/{run-page-BZ-VoP12.js → run-page-yA3m6QEA.js} +1 -1
  18. package/dist/assets/{scratchpad-panel-CCcUfWUz.js → scratchpad-panel-Cul3iUG0.js} +1 -1
  19. package/dist/assets/{session-panel-DaLr35Wc.js → session-panel-CzPIEKo8.js} +1 -1
  20. package/dist/assets/{snippets-panel-qn7otI-U.js → snippets-panel-DQgE3W8I.js} +1 -1
  21. package/dist/assets/{state-q7CKuEm6.js → state-C79RsVoe.js} +1 -1
  22. package/dist/assets/useNotebookActions-A-2lB6Y1.js +1 -0
  23. package/dist/index.html +6 -6
  24. package/package.json +1 -1
  25. package/src/components/data-table/data-table.tsx +12 -12
  26. package/src/components/editor/actions/pair-with-agent-modal.tsx +142 -0
  27. package/src/components/editor/actions/useNotebookActions.tsx +10 -0
  28. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  29. package/src/components/editor/chrome/panels/snippets-panel.tsx +1 -1
  30. package/src/components/editor/links/cell-link-list.tsx +1 -1
  31. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +2 -3
  32. package/src/components/editor/navigation/navigation.ts +2 -2
  33. package/src/components/editor/output/console/ConsoleOutput.tsx +1 -1
  34. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +62 -44
  35. package/src/plugins/impl/plotly/__tests__/PlotlyPlugin.test.tsx +114 -0
  36. package/src/plugins/impl/plotly/__tests__/selection.test.ts +158 -196
  37. package/src/plugins/impl/plotly/selection.ts +274 -56
  38. package/src/utils/mime-types.ts +1 -1
  39. package/dist/assets/common-B5GX57h6.js +0 -1
  40. package/dist/assets/index-BG82ditz.js +0 -35
  41. package/dist/assets/useNotebookActions-DB5vGtvM.js +0 -1
@@ -14,11 +14,16 @@ import useEvent from "react-use-event-hook";
14
14
  import { useDeepCompareMemoize } from "@/hooks/useDeepCompareMemoize";
15
15
  import { useScript } from "@/hooks/useScript";
16
16
  import { Arrays } from "@/utils/arrays";
17
- import { Objects } from "@/utils/objects";
18
17
  import {
19
- extractClickSelection,
20
18
  extractIndices,
21
19
  extractPoints,
20
+ extractSunburstPoints,
21
+ extractTreemapPoints,
22
+ hasPureLineTrace,
23
+ lineSelectionButtons,
24
+ type ModeBarButton,
25
+ mergeModeBarButtonsToAdd,
26
+ shouldHandleClickSelection,
22
27
  } from "./selection";
23
28
  import { usePlotlyLayout } from "./usePlotlyLayout";
24
29
 
@@ -35,6 +40,10 @@ type T =
35
40
  x?: number[];
36
41
  y?: number[];
37
42
  };
43
+ lasso?: {
44
+ x?: unknown[];
45
+ y?: unknown[];
46
+ };
38
47
  // These are kept in the state to persist selections across re-renders
39
48
  // on the frontend, but likely not used in the backend.
40
49
  selections?: unknown[];
@@ -77,23 +86,6 @@ const LazyPlot = lazy(() =>
77
86
  import("./Plot").then((mod) => ({ default: mod.Plot })),
78
87
  );
79
88
 
80
- const SUNBURST_DATA_KEYS: (keyof Plotly.SunburstPlotDatum)[] = [
81
- "color",
82
- "curveNumber",
83
- "entry",
84
- "hovertext",
85
- "id",
86
- "label",
87
- "parent",
88
- "percentEntry",
89
- "percentParent",
90
- "percentRoot",
91
- "pointNumber",
92
- "root",
93
- "value",
94
- ] as const;
95
- const TREE_MAP_DATA_KEYS = SUNBURST_DATA_KEYS;
96
-
97
89
  export const PlotlyComponent = memo(
98
90
  ({ figure: originalFigure, value, setValue, config }: PlotlyPluginProps) => {
99
91
  // Used for rendering LaTeX. TODO: Serve this library from Marimo
@@ -102,7 +94,7 @@ export const PlotlyComponent = memo(
102
94
  );
103
95
  const isScriptLoaded = scriptStatus === "ready";
104
96
 
105
- const { figure, layout, handleReset } = usePlotlyLayout({
97
+ const { figure, layout, setLayout, handleReset } = usePlotlyLayout({
106
98
  originalFigure,
107
99
  initialValue: value,
108
100
  isScriptLoaded,
@@ -112,31 +104,48 @@ export const PlotlyComponent = memo(
112
104
  handleReset();
113
105
  setValue({});
114
106
  });
107
+ const handleSetDragmode = useEvent(
108
+ (dragmode: Plotly.Layout["dragmode"]) => {
109
+ setLayout((prev) => ({ ...prev, dragmode }));
110
+ setValue((prev) => ({ ...prev, dragmode }));
111
+ },
112
+ );
115
113
 
116
114
  const configMemo = useDeepCompareMemoize(config);
117
115
  const plotlyConfig = useMemo((): Partial<Plotly.Config> => {
118
- return {
119
- displaylogo: false,
120
- modeBarButtonsToAdd: [
121
- // Custom button to reset the state
122
- {
123
- name: "reset",
124
- title: "Reset state",
125
- icon: {
126
- svg: `
116
+ const hasPureLine = hasPureLineTrace(figure.data);
117
+ const defaultButtons: ModeBarButton[] = [
118
+ // Custom button to reset the state
119
+ {
120
+ name: "reset",
121
+ title: "Reset state",
122
+ icon: {
123
+ svg: `
127
124
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
128
125
  stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw">
129
126
  <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
130
127
  <path d="M3 3v5h5" />
131
128
  </svg>`,
132
- },
133
- click: handleResetWithClear,
134
129
  },
135
- ],
130
+ click: handleResetWithClear,
131
+ },
132
+ ];
133
+ if (hasPureLine) {
134
+ defaultButtons.push(...lineSelectionButtons(handleSetDragmode));
135
+ }
136
+
137
+ return {
138
+ displaylogo: false,
136
139
  // Prioritize user's config
137
140
  ...configMemo,
141
+ modeBarButtonsToAdd: mergeModeBarButtonsToAdd(
142
+ defaultButtons,
143
+ configMemo.modeBarButtonsToAdd as
144
+ | readonly ModeBarButton[]
145
+ | undefined,
146
+ ),
138
147
  };
139
- }, [handleResetWithClear, configMemo]);
148
+ }, [handleResetWithClear, handleSetDragmode, configMemo, figure.data]);
140
149
 
141
150
  return (
142
151
  <LazyPlot
@@ -171,6 +180,7 @@ export const PlotlyComponent = memo(
171
180
  points: Arrays.EMPTY,
172
181
  indices: Arrays.EMPTY,
173
182
  range: undefined,
183
+ lasso: undefined,
174
184
  };
175
185
  });
176
186
  })}
@@ -181,9 +191,7 @@ export const PlotlyComponent = memo(
181
191
 
182
192
  setValue((prev) => ({
183
193
  ...prev,
184
- points: evt.points.map((point) =>
185
- Objects.pick(point, TREE_MAP_DATA_KEYS),
186
- ),
194
+ points: extractTreemapPoints(evt.points),
187
195
  }));
188
196
  })}
189
197
  onSunburstClick={useEvent((evt: Readonly<Plotly.PlotMouseEvent>) => {
@@ -193,9 +201,7 @@ export const PlotlyComponent = memo(
193
201
 
194
202
  setValue((prev) => ({
195
203
  ...prev,
196
- points: evt.points.map((point) =>
197
- Objects.pick(point, SUNBURST_DATA_KEYS),
198
- ),
204
+ points: extractSunburstPoints(evt.points),
199
205
  }));
200
206
  })}
201
207
  config={plotlyConfig}
@@ -203,13 +209,21 @@ export const PlotlyComponent = memo(
203
209
  if (!evt) {
204
210
  return;
205
211
  }
206
-
207
- const clickSelection = extractClickSelection(evt);
208
- if (!clickSelection) {
212
+ // Handle clicks for chart types where box/lasso selection
213
+ // is limited or unavailable (e.g. bar, heatmaps, histograms, pure line traces).
214
+ if (!shouldHandleClickSelection(evt.points)) {
209
215
  return;
210
216
  }
211
-
212
- setValue((prev) => ({ ...prev, ...clickSelection }));
217
+ const extractedPoints = extractPoints(evt.points);
218
+ const extractedIndices = extractIndices(evt.points);
219
+ setValue((prev) => ({
220
+ ...prev,
221
+ selections: Arrays.EMPTY,
222
+ range: undefined,
223
+ lasso: undefined,
224
+ points: extractedPoints,
225
+ indices: extractedIndices,
226
+ }));
213
227
  })}
214
228
  onSelected={useEvent((evt: Readonly<Plotly.PlotSelectionEvent>) => {
215
229
  if (!evt) {
@@ -223,6 +237,10 @@ export const PlotlyComponent = memo(
223
237
  points: extractPoints(evt.points),
224
238
  indices: extractIndices(evt.points),
225
239
  range: evt.range,
240
+ lasso:
241
+ "lassoPoints" in evt
242
+ ? (evt.lassoPoints as { x?: unknown[]; y?: unknown[] })
243
+ : undefined,
226
244
  }));
227
245
  })}
228
246
  className="w-full"
@@ -0,0 +1,114 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { act, render, waitFor } from "@testing-library/react";
4
+ import { Suspense } from "react";
5
+ import { describe, expect, it, vi } from "vitest";
6
+ import { SetupMocks } from "@/__mocks__/common";
7
+ import type { Setter } from "@/plugins/types";
8
+ import { PlotlyComponent } from "../PlotlyPlugin";
9
+
10
+ SetupMocks.resizeObserver();
11
+
12
+ type CapturedPlotProps = {
13
+ onClick?: (event: {
14
+ points: {
15
+ data?: { type?: string };
16
+ x?: string | number;
17
+ y?: string | number;
18
+ pointIndex?: number;
19
+ pointNumber?: number;
20
+ curveNumber?: number;
21
+ }[];
22
+ }) => void;
23
+ } | null;
24
+
25
+ let capturedPlotProps: CapturedPlotProps = null;
26
+
27
+ vi.mock("../Plot", () => ({
28
+ Plot: (props: CapturedPlotProps) => {
29
+ capturedPlotProps = props;
30
+ return <div data-testid="plotly-mock" />;
31
+ },
32
+ }));
33
+
34
+ vi.mock("../usePlotlyLayout", () => ({
35
+ usePlotlyLayout: ({
36
+ originalFigure,
37
+ }: {
38
+ originalFigure: {
39
+ data: unknown[];
40
+ layout: Record<string, unknown>;
41
+ frames: unknown[] | null;
42
+ };
43
+ }) => ({
44
+ figure: originalFigure,
45
+ layout: originalFigure.layout,
46
+ handleReset: vi.fn(),
47
+ }),
48
+ }));
49
+
50
+ vi.mock("@/hooks/useScript", () => ({
51
+ useScript: () => "ready",
52
+ }));
53
+
54
+ vi.mock("react-use-event-hook", () => ({
55
+ default: <T,>(callback: T) => callback,
56
+ }));
57
+
58
+ describe("PlotlyPlugin", () => {
59
+ it("clicking a bar selects that bar", async () => {
60
+ const setValue = vi.fn<Setter<unknown>>();
61
+
62
+ render(
63
+ <Suspense fallback={null}>
64
+ <PlotlyComponent
65
+ figure={{
66
+ data: [{ type: "bar" }],
67
+ layout: {},
68
+ frames: null,
69
+ }}
70
+ value={undefined}
71
+ setValue={setValue}
72
+ host={document.createElement("div")}
73
+ config={{}}
74
+ />
75
+ </Suspense>,
76
+ );
77
+
78
+ await waitFor(() => {
79
+ expect(capturedPlotProps).not.toBeNull();
80
+ });
81
+
82
+ act(() => {
83
+ capturedPlotProps?.onClick?.({
84
+ points: [
85
+ {
86
+ data: { type: "bar" },
87
+ x: "Feb",
88
+ y: 18,
89
+ pointIndex: 1,
90
+ pointNumber: 1,
91
+ curveNumber: 0,
92
+ },
93
+ ],
94
+ });
95
+ });
96
+
97
+ expect(setValue).toHaveBeenCalledTimes(1);
98
+ const updater = setValue.mock.calls[0][0] as (value: unknown) => unknown;
99
+ expect(updater({})).toEqual({
100
+ selections: [],
101
+ points: [
102
+ {
103
+ x: "Feb",
104
+ y: 18,
105
+ curveNumber: 0,
106
+ pointNumber: 1,
107
+ pointIndex: 1,
108
+ },
109
+ ],
110
+ indices: [1],
111
+ range: undefined,
112
+ });
113
+ });
114
+ });