@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
@@ -2,20 +2,29 @@
2
2
 
3
3
  import { pick } from "lodash-es";
4
4
  import type * as Plotly from "plotly.js";
5
- import { Arrays } from "@/utils/arrays";
5
+
6
6
  import { createParser, type PlotlyTemplateParser } from "./parse-from-template";
7
7
 
8
8
  type AxisName = string;
9
9
  type AxisDatum = unknown;
10
10
 
11
- export interface PlotlyClickSelection {
12
- points: Record<AxisName, AxisDatum>[] | Plotly.PlotDatum[];
13
- indices: number[];
14
- range: undefined;
15
- selections: unknown[];
16
- }
11
+ const SUNBURST_DATA_KEYS: (keyof Plotly.SunburstPlotDatum)[] = [
12
+ "color",
13
+ "curveNumber",
14
+ "entry",
15
+ "hovertext",
16
+ "id",
17
+ "label",
18
+ "parent",
19
+ "percentEntry",
20
+ "percentParent",
21
+ "percentRoot",
22
+ "pointNumber",
23
+ "root",
24
+ "value",
25
+ ] as const;
17
26
 
18
- const CLICK_SELECTABLE_TRACE_TYPES = new Set(["heatmap", "histogram"]);
27
+ const LINE_CLICK_TRACE_TYPES = new Set(["scatter", "scattergl"]);
19
28
 
20
29
  const STANDARD_POINT_KEYS: string[] = [
21
30
  "x",
@@ -29,61 +38,282 @@ const STANDARD_POINT_KEYS: string[] = [
29
38
  "pointIndex",
30
39
  ] as const;
31
40
 
41
+ type PointWithFullData = Plotly.PlotDatum & {
42
+ pointNumbers?: number[];
43
+ fullData?: {
44
+ type?: string;
45
+ mode?: string;
46
+ x?: unknown[];
47
+ y?: unknown[];
48
+ hovertemplate?: string | string[];
49
+ };
50
+ };
51
+
52
+ interface TraceSource {
53
+ type?: string;
54
+ mode?: string;
55
+ x?: unknown[];
56
+ y?: unknown[];
57
+ hovertemplate?: string | string[];
58
+ }
59
+
60
+ export type ModeBarButton = NonNullable<
61
+ Plotly.Config["modeBarButtonsToAdd"]
62
+ >[number];
63
+
64
+ function coalesceField<T>(
65
+ primary: T | undefined,
66
+ fallback: T | undefined,
67
+ ): T | undefined {
68
+ return primary ?? fallback;
69
+ }
70
+
71
+ function getTraceSource(point: Plotly.PlotDatum): TraceSource {
72
+ const withFullData = point as PointWithFullData;
73
+ const data = (point.data ?? {}) as TraceSource;
74
+ const fullData = (withFullData.fullData ?? {}) as TraceSource;
75
+
76
+ // Plotly click payloads sometimes include partial `data` plus richer `fullData`.
77
+ // Merge field-by-field so we don't lose type/mode/x/y metadata for pure lines.
78
+ return {
79
+ type: coalesceField(data.type, fullData.type),
80
+ mode: coalesceField(data.mode, fullData.mode),
81
+ x: coalesceField(data.x, fullData.x),
82
+ y: coalesceField(data.y, fullData.y),
83
+ hovertemplate: coalesceField(data.hovertemplate, fullData.hovertemplate),
84
+ };
85
+ }
86
+
87
+ function asFiniteNumber(value: unknown): number | undefined {
88
+ return typeof value === "number" && Number.isFinite(value)
89
+ ? value
90
+ : undefined;
91
+ }
92
+
32
93
  function getPointIndex(point: Plotly.PlotDatum): number | undefined {
33
- if (typeof point.pointIndex === "number") {
34
- return point.pointIndex;
94
+ const pointIndex = asFiniteNumber(point.pointIndex);
95
+ if (pointIndex !== undefined) {
96
+ return pointIndex;
35
97
  }
36
98
 
37
- if (typeof point.pointNumber === "number") {
38
- return point.pointNumber;
99
+ const pointNumber = asFiniteNumber(point.pointNumber);
100
+ if (pointNumber !== undefined) {
101
+ return pointNumber;
39
102
  }
40
103
 
41
- return undefined;
104
+ const pointNumbers = (point as PointWithFullData).pointNumbers;
105
+ if (!Array.isArray(pointNumbers)) {
106
+ return undefined;
107
+ }
108
+
109
+ return pointNumbers.map(asFiniteNumber).find((n) => n !== undefined);
110
+ }
111
+
112
+ function isLinePoint(point: Plotly.PlotDatum): boolean {
113
+ const trace = getTraceSource(point);
114
+ if (!LINE_CLICK_TRACE_TYPES.has(String(trace.type))) {
115
+ return false;
116
+ }
117
+
118
+ const mode = trace.mode;
119
+ if (typeof mode !== "string") {
120
+ // Some Plotly click payloads omit mode on point.data, especially with
121
+ // line traces; treat scatter/scattergl as line-like in this case.
122
+ return true;
123
+ }
124
+
125
+ return mode.split("+").includes("lines");
42
126
  }
43
127
 
44
- function isClickSelectablePoint(point: Plotly.PlotDatum): boolean {
45
- const traceType = point.data?.type;
46
- return typeof traceType === "string"
47
- ? CLICK_SELECTABLE_TRACE_TYPES.has(traceType)
48
- : false;
128
+ function isPureLineMode(mode: unknown): boolean {
129
+ if (typeof mode !== "string") {
130
+ return false;
131
+ }
132
+ const parts = mode.split("+");
133
+ return parts.includes("lines") && !parts.includes("markers");
49
134
  }
50
135
 
51
- export function extractIndices(points: Plotly.PlotDatum[]): number[] {
52
- return points.flatMap((point) => {
53
- const index = getPointIndex(point);
54
- return typeof index === "number" ? [index] : [];
136
+ export function hasPureLineTrace(
137
+ data: readonly Plotly.Data[] | undefined,
138
+ ): boolean {
139
+ if (!data) {
140
+ return false;
141
+ }
142
+
143
+ return data.some((trace) => {
144
+ const traceType = (trace as { type?: unknown }).type;
145
+ const isScatterLike =
146
+ traceType === undefined || LINE_CLICK_TRACE_TYPES.has(String(traceType));
147
+ if (!isScatterLike) {
148
+ return false;
149
+ }
150
+ return isPureLineMode((trace as { mode?: unknown }).mode);
55
151
  });
56
152
  }
57
153
 
58
- /**
59
- * This is a hack to extract the points with their original keys,
60
- * instead of the ones that Plotly uses internally,
61
- * by using the hovertemplate.
62
- */
63
- export function extractPoints(
64
- points: Plotly.PlotDatum[],
65
- ): Record<AxisName, AxisDatum>[] {
66
- if (!points) {
67
- return [];
154
+ function createDragmodeButton(
155
+ name: string,
156
+ title: string,
157
+ svg: string,
158
+ dragmode: Plotly.Layout["dragmode"],
159
+ setDragmode: (dragmode: Plotly.Layout["dragmode"]) => void,
160
+ ): ModeBarButton {
161
+ return {
162
+ name,
163
+ title,
164
+ icon: { svg },
165
+ click: () => setDragmode(dragmode),
166
+ };
167
+ }
168
+
169
+ export function lineSelectionButtons(
170
+ setDragmode: (dragmode: Plotly.Layout["dragmode"]) => void,
171
+ ): ModeBarButton[] {
172
+ return [
173
+ createDragmodeButton(
174
+ "line-box-select",
175
+ "Box select",
176
+ `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
177
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
178
+ <rect x="4" y="4" width="16" height="16" stroke-dasharray="2 2" />
179
+ </svg>`,
180
+ "select",
181
+ setDragmode,
182
+ ),
183
+ createDragmodeButton(
184
+ "line-lasso-select",
185
+ "Lasso select",
186
+ `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
187
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
188
+ <path d="M6 8c0-2.2 2.2-4 5-4s5 1.8 5 4-2.2 4-5 4-5 1.8-5 4 2.2 4 5 4" />
189
+ <circle cx="17.5" cy="16.5" r="1.5" />
190
+ </svg>`,
191
+ "lasso",
192
+ setDragmode,
193
+ ),
194
+ ];
195
+ }
196
+
197
+ export function mergeModeBarButtonsToAdd(
198
+ defaults: readonly ModeBarButton[],
199
+ userButtons: readonly ModeBarButton[] | undefined,
200
+ ): ModeBarButton[] {
201
+ const merged: ModeBarButton[] = [];
202
+ const seenStrings = new Set<string>();
203
+
204
+ const pushButton = (button: ModeBarButton) => {
205
+ if (typeof button === "string") {
206
+ if (seenStrings.has(button)) {
207
+ return;
208
+ }
209
+ seenStrings.add(button);
210
+ merged.push(button);
211
+ return;
212
+ }
213
+ merged.push(button);
214
+ };
215
+
216
+ defaults.forEach(pushButton);
217
+ userButtons?.forEach(pushButton);
218
+ return merged;
219
+ }
220
+
221
+ export function shouldHandleClickSelection(
222
+ points: readonly Plotly.PlotDatum[],
223
+ ): boolean {
224
+ return points.some((point) => {
225
+ const type = getTraceSource(point).type;
226
+ return (
227
+ type === "bar" ||
228
+ type === "heatmap" ||
229
+ type === "histogram" ||
230
+ isLinePoint(point)
231
+ );
232
+ });
233
+ }
234
+
235
+ export function extractIndices(points: readonly Plotly.PlotDatum[]): number[] {
236
+ return points
237
+ .map(getPointIndex)
238
+ .filter((pointIndex): pointIndex is number => pointIndex !== undefined);
239
+ }
240
+
241
+ function getIndexedValue(series: unknown, index: number): unknown {
242
+ if (Array.isArray(series) || ArrayBuffer.isView(series)) {
243
+ return (series as ArrayLike<unknown>)[index];
244
+ }
245
+ if (typeof series === "object" && series !== null && "length" in series) {
246
+ const maybeLength = Number(
247
+ (series as { length?: unknown }).length ?? Number.NaN,
248
+ );
249
+ if (Number.isFinite(maybeLength) && index >= 0 && index < maybeLength) {
250
+ return (series as Record<number, unknown>)[index];
251
+ }
252
+ }
253
+ return undefined;
254
+ }
255
+
256
+ function withInferredXY(
257
+ point: Plotly.PlotDatum,
258
+ fields: Record<AxisName, AxisDatum>,
259
+ ): Record<AxisName, AxisDatum> {
260
+ // For some pure-line clicks Plotly provides index metadata but omits x/y.
261
+ // Recover x/y from trace arrays so Python gets a stable payload.
262
+ if (fields.x !== undefined && fields.y !== undefined) {
263
+ return fields;
264
+ }
265
+
266
+ const pointIndex = getPointIndex(point);
267
+ if (pointIndex === undefined) {
268
+ return fields;
269
+ }
270
+
271
+ const nextFields: Record<AxisName, AxisDatum> = { ...fields };
272
+ if (nextFields.pointIndex === undefined) {
273
+ nextFields.pointIndex = pointIndex;
274
+ }
275
+
276
+ const trace = getTraceSource(point);
277
+ if (nextFields.x === undefined) {
278
+ const inferredX = getIndexedValue(trace.x, pointIndex);
279
+ if (inferredX !== undefined) {
280
+ nextFields.x = inferredX;
281
+ }
282
+ }
283
+ if (nextFields.y === undefined) {
284
+ const inferredY = getIndexedValue(trace.y, pointIndex);
285
+ if (inferredY !== undefined) {
286
+ nextFields.y = inferredY;
287
+ }
68
288
  }
69
289
 
290
+ return nextFields;
291
+ }
292
+
293
+ export function extractPoints(
294
+ points: readonly Plotly.PlotDatum[],
295
+ ): Record<AxisName, AxisDatum>[] {
70
296
  let parser: PlotlyTemplateParser | undefined;
71
297
 
72
298
  return points.map((point) => {
73
- const standardPointFields = pick(point, STANDARD_POINT_KEYS);
299
+ const standardPointFields = withInferredXY(
300
+ point,
301
+ pick(point, STANDARD_POINT_KEYS),
302
+ );
303
+
304
+ const trace = getTraceSource(point);
74
305
 
75
306
  // Get the first hovertemplate
76
- const hovertemplate = Array.isArray(point.data.hovertemplate)
77
- ? point.data.hovertemplate[0]
78
- : point.data.hovertemplate;
307
+ const hovertemplate = Array.isArray(trace.hovertemplate)
308
+ ? trace.hovertemplate[0]
309
+ : trace.hovertemplate;
79
310
 
80
311
  // For chart types with standard point keys (e.g. heatmaps),
81
312
  // or when there's no hovertemplate, pick keys directly from the point.
82
- if (!hovertemplate || point.data?.type === "heatmap") {
313
+ if (!hovertemplate || trace.type === "heatmap") {
83
314
  return standardPointFields;
84
315
  }
85
316
 
86
- // Update or create a parser
87
317
  parser = parser
88
318
  ? parser.update(hovertemplate)
89
319
  : createParser(hovertemplate);
@@ -94,22 +324,10 @@ export function extractPoints(
94
324
  });
95
325
  }
96
326
 
97
- export function extractClickSelection(
98
- evt: Readonly<Plotly.PlotMouseEvent>,
99
- ): PlotlyClickSelection | undefined {
100
- if (!evt.points?.length) {
101
- return undefined;
102
- }
103
-
104
- const points = evt.points.filter(isClickSelectablePoint);
105
- if (points.length === 0) {
106
- return undefined;
107
- }
108
-
109
- return {
110
- selections: Arrays.EMPTY,
111
- points: extractPoints(points),
112
- indices: extractIndices(points),
113
- range: undefined,
114
- };
327
+ export function extractSunburstPoints(
328
+ points: readonly Plotly.PlotDatum[],
329
+ ): Record<string, unknown>[] {
330
+ return points.map((point) => pick(point, SUNBURST_DATA_KEYS));
115
331
  }
332
+
333
+ export const extractTreemapPoints = extractSunburstPoints;
@@ -151,7 +151,7 @@ export function sortByPrecedence<T>(
151
151
  ): [MimeType, T][] {
152
152
  const unknownPrecedence = precedence.size;
153
153
 
154
- return [...entries].toSorted((a, b) => {
154
+ return entries.toSorted((a, b) => {
155
155
  const indexA = precedence.get(a[0]) ?? unknownPrecedence;
156
156
  const indexB = precedence.get(b[0]) ?? unknownPrecedence;
157
157
  return indexA - indexB;
@@ -1 +0,0 @@
1
- import{s as v}from"./chunk-LvLJmgfZ.js";import{t as w}from"./react-Bj1aDYRI.js";import{j as k}from"./cells-ArUhhHls.js";import{t as g}from"./compiler-runtime-B3qBwwSJ.js";import{t as N}from"./jsx-runtime-Blw4afVn.js";import{i as y,r as C,t as _}from"./popover-AtoFZ7i4.js";import{t as b}from"./badge-CufG8W4J.js";import{t as S}from"./use-toast-T0_cQDma.js";import{n as I}from"./copy-C8iWa1qI.js";import{t as j}from"./cell-link-D4UrIH9w.js";var O=g(),i=v(N(),1);const B=p=>{let e=(0,O.c)(18),{maxCount:n,cellIds:r,onClick:t,skipScroll:o}=p,m=k(),l,a,c;if(e[0]!==r||e[1]!==m||e[2]!==n||e[3]!==t||e[4]!==o){c=Symbol.for("react.early_return_sentinel");e:{let f;e[8]===m?f=e[9]:(f=(s,x)=>m.inOrderIds.indexOf(s)-m.inOrderIds.indexOf(x),e[8]=m,e[9]=f);let u=[...r].toSorted(f);if(r.length===0){let s;e[10]===Symbol.for("react.memo_cache_sentinel")?(s=(0,i.jsx)("div",{className:"text-muted-foreground",children:"--"}),e[10]=s):s=e[10],c=s;break e}let h;e[11]!==r.length||e[12]!==t||e[13]!==o?(h=(s,x)=>(0,i.jsxs)("span",{className:"truncate",children:[(0,i.jsx)(j,{variant:"focus",cellId:s,skipScroll:o,className:"whitespace-nowrap",onClick:t?()=>t(s):void 0},s),x<r.length-1&&", "]},s),e[11]=r.length,e[12]=t,e[13]=o,e[14]=h):h=e[14],l=u.slice(0,n).map(h),a=r.length>n&&(0,i.jsxs)(_,{children:[(0,i.jsx)(y,{asChild:!0,children:(0,i.jsxs)("span",{className:"whitespace-nowrap text-muted-foreground text-xs hover:underline cursor-pointer",children:["+",r.length-n," more"]})}),(0,i.jsx)(C,{className:"w-auto",children:(0,i.jsx)("div",{className:"flex flex-col gap-1 py-1",children:u.slice(n).map(s=>(0,i.jsx)(j,{variant:"focus",cellId:s,className:"whitespace-nowrap",onClick:t?()=>t(s):void 0},s))})})]})}e[0]=r,e[1]=m,e[2]=n,e[3]=t,e[4]=o,e[5]=l,e[6]=a,e[7]=c}else l=e[5],a=e[6],c=e[7];if(c!==Symbol.for("react.early_return_sentinel"))return c;let d;return e[15]!==l||e[16]!==a?(d=(0,i.jsxs)(i.Fragment,{children:[l,a]}),e[15]=l,e[16]=a,e[17]=d):d=e[17],d};var F=g();w();const q=p=>{let e=(0,F.c)(15),n,r,t,o;e[0]===p?(n=e[1],r=e[2],t=e[3],o=e[4]):({name:r,declaredBy:n,onClick:t,...o}=p,e[0]=p,e[1]=n,e[2]=r,e[3]=t,e[4]=o);let m=n.length>1?"destructive":"outline",l;e[5]!==r||e[6]!==t?(l=async d=>{if(t){t(d);return}await I(r),S({title:"Copied to clipboard"})},e[5]=r,e[6]=t,e[7]=l):l=e[7];let a;e[8]!==r||e[9]!==m||e[10]!==l?(a=(0,i.jsx)(b,{title:r,variant:m,className:"rounded-sm text-ellipsis block overflow-hidden max-w-fit cursor-pointer font-medium",onClick:l,children:r}),e[8]=r,e[9]=m,e[10]=l,e[11]=a):a=e[11];let c;return e[12]!==o||e[13]!==a?(c=(0,i.jsx)("div",{className:"max-w-[130px]",...o,children:a}),e[12]=o,e[13]=a,e[14]=c):c=e[14],c};export{B as n,q as t};