@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
@@ -1,237 +1,199 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import type * as Plotly from "plotly.js";
4
- import { describe, expect, it } from "vitest";
4
+ import { describe, expect, it, vi } from "vitest";
5
5
  import {
6
- extractClickSelection,
7
6
  extractIndices,
8
7
  extractPoints,
8
+ hasPureLineTrace,
9
+ lineSelectionButtons,
10
+ type ModeBarButton,
11
+ mergeModeBarButtonsToAdd,
12
+ shouldHandleClickSelection,
9
13
  } from "../selection";
10
14
 
11
- interface PlotlyPointInput {
12
- data: {
13
- type: string;
14
- hovertemplate?: string | string[];
15
- };
16
- [key: string]: unknown;
15
+ function createTrace(trace: Partial<Plotly.PlotData>): Plotly.Data {
16
+ return trace as unknown as Plotly.Data;
17
17
  }
18
18
 
19
- function makePoint(point: PlotlyPointInput): Plotly.PlotDatum {
20
- return point as unknown as Plotly.PlotDatum;
19
+ function createPlotDatum<T extends object>(overrides: T): Plotly.PlotDatum & T {
20
+ return overrides as unknown as Plotly.PlotDatum & T;
21
21
  }
22
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
- ];
23
+ describe("hasPureLineTrace", () => {
24
+ it("detects scatter and scattergl traces that are pure lines", () => {
25
+ expect(
26
+ hasPureLineTrace([
27
+ createTrace({ type: "scatter", mode: "lines" }),
28
+ createTrace({ type: "scattergl", mode: "text+lines" }),
29
+ ]),
30
+ ).toBe(true);
31
+ });
45
32
 
46
- expect(extractIndices(points)).toEqual([2, 4]);
33
+ it("ignores non-line and marker traces", () => {
34
+ expect(
35
+ hasPureLineTrace([
36
+ createTrace({ type: "scatter", mode: "markers" }),
37
+ createTrace({ type: "scatter", mode: "lines+markers" }),
38
+ createTrace({ type: "bar" }),
39
+ ]),
40
+ ).toBe(false);
47
41
  });
48
42
  });
49
43
 
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
- ];
44
+ describe("lineSelectionButtons", () => {
45
+ it("creates dragmode buttons that update dragmode", () => {
46
+ const setDragmode = vi.fn();
47
+ const buttons = lineSelectionButtons(setDragmode);
66
48
 
67
- expect(extractPoints(points)).toEqual([
68
- {
69
- x: 3,
70
- y: 7,
71
- curveNumber: 0,
72
- pointIndex: 1,
73
- label: "B",
74
- },
75
- ]);
76
- });
49
+ expect(buttons).toHaveLength(2);
50
+ expect(
51
+ buttons.map((button) =>
52
+ typeof button === "string" ? button : button.name,
53
+ ),
54
+ ).toEqual(["line-box-select", "line-lasso-select"]);
77
55
 
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
- ];
56
+ const graphDiv = document.createElement(
57
+ "div",
58
+ ) as unknown as Plotly.PlotlyHTMLElement;
59
+ const clickEvent = new MouseEvent("click");
89
60
 
90
- expect(extractPoints(points)).toEqual([
91
- {
92
- x: "B",
93
- y: "Row 2",
94
- z: 6,
95
- curveNumber: 0,
96
- pointIndex: 5,
97
- },
98
- ]);
61
+ (buttons[0] as Exclude<ModeBarButton, string>).click(graphDiv, clickEvent);
62
+ (buttons[1] as Exclude<ModeBarButton, string>).click(graphDiv, clickEvent);
63
+
64
+ expect(setDragmode).toHaveBeenNthCalledWith(1, "select");
65
+ expect(setDragmode).toHaveBeenNthCalledWith(2, "lasso");
99
66
  });
100
67
  });
101
68
 
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
- ]);
69
+ describe("mergeModeBarButtonsToAdd", () => {
70
+ it("deduplicates string buttons while preserving custom buttons", () => {
71
+ const customButton = {
72
+ name: "custom",
73
+ title: "Custom",
74
+ icon: { svg: "<svg />" },
75
+ click: vi.fn(),
76
+ } satisfies Exclude<ModeBarButton, string>;
77
+
78
+ expect(
79
+ mergeModeBarButtonsToAdd(
80
+ ["zoom2d", "lasso2d"],
81
+ ["lasso2d", customButton, "zoom2d"],
82
+ ),
83
+ ).toEqual(["zoom2d", "lasso2d", customButton]);
84
+ });
85
+ });
112
86
 
113
- expect(extractClickSelection(event)).toBeUndefined();
87
+ describe("shouldHandleClickSelection", () => {
88
+ it("accepts bar clicks", () => {
89
+ const barPoint = createPlotDatum({
90
+ data: { type: "bar" },
91
+ });
92
+
93
+ expect(shouldHandleClickSelection([barPoint])).toBe(true);
114
94
  });
115
95
 
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
- ]);
96
+ it("accepts heatmap clicks", () => {
97
+ const heatmapPoint = createPlotDatum({
98
+ data: { type: "heatmap" },
99
+ });
142
100
 
143
- expect(extractClickSelection(event)).toBeUndefined();
101
+ expect(shouldHandleClickSelection([heatmapPoint])).toBe(true);
144
102
  });
145
103
 
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
- ]);
104
+ it("accepts histogram clicks", () => {
105
+ const histogramPoint = createPlotDatum({
106
+ data: { type: "histogram" },
107
+ });
108
+
109
+ expect(shouldHandleClickSelection([histogramPoint])).toBe(true);
110
+ });
165
111
 
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
- ],
112
+ it("accepts scatter clicks when Plotly omits mode", () => {
113
+ const linePoint = createPlotDatum({
114
+ data: { type: "scatter" },
179
115
  });
116
+
117
+ expect(shouldHandleClickSelection([linePoint])).toBe(true);
180
118
  });
181
119
 
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
- ]);
120
+ it("rejects non-line scatter marker clicks", () => {
121
+ const markerPoint = createPlotDatum({
122
+ data: { type: "scatter", mode: "markers" },
123
+ });
193
124
 
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
- ],
125
+ expect(shouldHandleClickSelection([markerPoint])).toBe(false);
126
+ });
127
+ });
128
+
129
+ describe("extractIndices", () => {
130
+ it("prefers pointIndex and falls back to pointNumber and pointNumbers", () => {
131
+ const points = [
132
+ createPlotDatum({ pointIndex: 2 }),
133
+ createPlotDatum({ pointNumber: 5 }),
134
+ createPlotDatum({ pointNumbers: [Number.NaN, 8] }),
135
+ createPlotDatum({ pointNumbers: [Infinity] }),
136
+ ];
137
+
138
+ expect(extractIndices(points)).toEqual([2, 5, 8]);
139
+ });
140
+ });
141
+
142
+ describe("extractPoints", () => {
143
+ it("infers missing x/y from trace data for line clicks", () => {
144
+ const point = createPlotDatum({
145
+ pointNumber: 1,
146
+ data: { type: "scatter" },
147
+ fullData: {
148
+ type: "scatter",
149
+ mode: "lines",
150
+ x: new Float64Array([10, 20, 30]),
151
+ y: [100, 200, 300],
152
+ },
207
153
  });
154
+
155
+ expect(extractPoints([point])).toEqual([
156
+ { pointNumber: 1, pointIndex: 1, x: 20, y: 200 },
157
+ ]);
208
158
  });
209
159
 
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
- }),
160
+ it("parses hovertemplate values while keeping inferred point fields", () => {
161
+ const point = createPlotDatum({
162
+ pointIndex: 0,
163
+ customdata: ["Mustang", "USA"],
164
+ fullData: {
165
+ type: "scatter",
166
+ mode: "lines",
167
+ x: ["300"],
168
+ y: ["30"],
169
+ hovertemplate:
170
+ "Name=%{customdata[0]}<br>Origin=%{customdata[1]}<extra></extra>",
171
+ },
172
+ });
173
+
174
+ expect(extractPoints([point])).toEqual([
175
+ {
176
+ pointIndex: 0,
177
+ x: "300",
178
+ y: "30",
179
+ Name: "Mustang",
180
+ Origin: "USA",
181
+ },
220
182
  ]);
183
+ });
221
184
 
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
- ],
185
+ it("returns only standard fields for heatmaps", () => {
186
+ const point = createPlotDatum({
187
+ x: 1,
188
+ y: 2,
189
+ z: 3,
190
+ text: "ignored",
191
+ data: {
192
+ type: "heatmap",
193
+ hovertemplate: "Label=%{text}<extra></extra>",
194
+ },
235
195
  });
196
+
197
+ expect(extractPoints([point])).toEqual([{ x: 1, y: 2, z: 3 }]);
236
198
  });
237
199
  });