@marimo-team/frontend 0.22.1-dev34 → 0.22.1-dev39

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 (239) hide show
  1. package/dist/assets/{CellStatus-DKyVv7Zj.js → CellStatus-Cf0Jlrcs.js} +1 -1
  2. package/dist/assets/{ConnectedDataExplorerComponent-BUc9LHJJ.js → ConnectedDataExplorerComponent-DUxaLoL7.js} +1 -1
  3. package/dist/assets/{JsonOutput-D1i8P1dG.js → JsonOutput-IpD2GLtO.js} +2 -2
  4. package/dist/assets/{MarimoErrorOutput-RmM7djc-.js → MarimoErrorOutput-dTNCLY-Q.js} +1 -1
  5. package/dist/assets/{Plot-C7NE7pEx.js → Plot-BAM1jEAz.js} +72 -72
  6. package/dist/assets/{RenderHTML-Bmn77an6.js → RenderHTML-C5GEp4ca.js} +1 -1
  7. package/dist/assets/{add-cell-with-ai-RyZ9Xe2b.js → add-cell-with-ai-C0J3LyiV.js} +1 -1
  8. package/dist/assets/{add-connection-dialog-ogwy8tvS.js → add-connection-dialog-C42PDYI7.js} +1 -1
  9. package/dist/assets/{agent-panel-BHLkHj7k.js → agent-panel-CH-jHjEl.js} +1 -1
  10. package/dist/assets/{ai-model-dropdown-Dnf-CxbP.js → ai-model-dropdown-D14GiszF.js} +1 -1
  11. package/dist/assets/{app-config-button-D1Z_xsvC.js → app-config-button-v-557oRb.js} +1 -1
  12. package/dist/assets/{cell-editor-_GDTh-4a.js → cell-editor-Dd6UaL1A.js} +2 -2
  13. package/dist/assets/{cell-link-D4UrIH9w.js → cell-link-Cimoe3Fv.js} +1 -1
  14. package/dist/assets/{cells-ArUhhHls.js → cells-CcsG9Aum.js} +1 -1
  15. package/dist/assets/{chat-display-YmFjOXkV.js → chat-display-hfpeXiYe.js} +1 -1
  16. package/dist/assets/{chat-panel-B76rxYTh.js → chat-panel-DxT370nA.js} +1 -1
  17. package/dist/assets/{chat-ui-BitNq1z6.js → chat-ui-Dv4y0-td.js} +1 -1
  18. package/dist/assets/{column-preview-NDhbeu0E.js → column-preview-ZSErTRFA.js} +1 -1
  19. package/dist/assets/{command-palette-uJxkhle4.js → command-palette-CjF_cblG.js} +1 -1
  20. package/dist/assets/{common-CRBlPqv5.js → common-BZK7spst.js} +1 -1
  21. package/dist/assets/{components-DwGcJvMB.js → components-D2OlyENc.js} +1 -1
  22. package/dist/assets/{components-BfHGr__b.js → components-DpxyscxU.js} +1 -1
  23. package/dist/assets/{datasource-D9e37ifa.js → datasource-D9nfSxKS.js} +1 -1
  24. package/dist/assets/{dependency-graph-panel-BBmN-Vc7.js → dependency-graph-panel-5MbMtFss.js} +1 -1
  25. package/dist/assets/{documentation-panel-Dm2xtsTq.js → documentation-panel-DPdXS3YO.js} +1 -1
  26. package/dist/assets/{download-7EtMZf2Y.js → download-BFQaUFKI.js} +1 -1
  27. package/dist/assets/{edit-page-Cr_DnrkM.js → edit-page-CMd8_Psc.js} +4 -4
  28. package/dist/assets/{error-panel-C3-vTzaH.js → error-panel-DV8jpRsf.js} +1 -1
  29. package/dist/assets/{file-explorer-panel-g9KppC7Y.js → file-explorer-panel-Dr_ZNDk3.js} +1 -1
  30. package/dist/assets/{file-icons-JXAG6vK-.js → file-icons-DSJsG_mI.js} +1 -1
  31. package/dist/assets/{floating-outline-B6Qyid7Q.js → floating-outline-Cfa1ESSb.js} +1 -1
  32. package/dist/assets/{focus-DCN0oEe0.js → focus-C5u0JQUq.js} +1 -1
  33. package/dist/assets/{form-BVnQnVQ2.js → form-3ZUGKch9.js} +1 -1
  34. package/dist/assets/glide-data-editor-a3qLDl-r.js +132 -0
  35. package/dist/assets/{home-page-Bg02jazh.js → home-page-BrqppCUS.js} +1 -1
  36. package/dist/assets/{hooks-zQJ9iU_R.js → hooks-CHE17GG1.js} +1 -1
  37. package/dist/assets/{html-to-image-C5XSE7QT.js → html-to-image-BRbQwG7G.js} +1 -1
  38. package/dist/assets/{index-BaQAJwyb.css → index-BkdonYlq.css} +1 -1
  39. package/dist/assets/index-Bt8G6SSE.js +42 -0
  40. package/dist/assets/{kiosk-mode-CriCUOI1.js → kiosk-mode-Cu86-jaD.js} +1 -1
  41. package/dist/assets/{layout-Cudicm29.js → layout-B-lTkLKA.js} +1 -1
  42. package/dist/assets/{logs-panel-CoWO9c8s.js → logs-panel-CNVgwoHO.js} +1 -1
  43. package/dist/assets/{markdown-renderer-CyUc0f8D.js → markdown-renderer-B91NzmVT.js} +1 -1
  44. package/dist/assets/{name-cell-input-DeM6upjB.js → name-cell-input-hsV_b1Op.js} +1 -1
  45. package/dist/assets/{outline-panel-C4P3wA7Z.js → outline-panel-C3jvSnZF.js} +1 -1
  46. package/dist/assets/{packages-panel-DnrjjQDF.js → packages-panel-0_Z5vM7i.js} +1 -1
  47. package/dist/assets/{panels-BCRqI88j.js → panels-uc8QhzpO.js} +1 -1
  48. package/dist/assets/{process-output-e_aMblRk.js → process-output-CsvKn_Mr.js} +1 -1
  49. package/dist/assets/{readonly-python-code-CVgh88Tn.js → readonly-python-code-oFCRG7Wt.js} +1 -1
  50. package/dist/assets/{run-page-C-ySBXLo.js → run-page-DKCqiq_f.js} +1 -1
  51. package/dist/assets/{scratchpad-panel-C5Pogi1P.js → scratchpad-panel-ChHLgDv_.js} +1 -1
  52. package/dist/assets/{session-panel-DEsnZWks.js → session-panel-CPKKt31q.js} +1 -1
  53. package/dist/assets/{snippets-panel-DQgE3W8I.js → snippets-panel-CDqEDwpY.js} +1 -1
  54. package/dist/assets/{spec-CBbUxOvL.js → spec-CFx2bitO.js} +1 -1
  55. package/dist/assets/{state-pGNeffyB.js → state-BBVlYaRC.js} +1 -1
  56. package/dist/assets/{state-CV8Wy3e4.js → state-C93JW11U.js} +1 -1
  57. package/dist/assets/{textarea-DqzNK0s9.js → textarea-DJEKmYtw.js} +1 -1
  58. package/dist/assets/{tracing-BUFVOmZw.js → tracing-CLWi5jD3.js} +1 -1
  59. package/dist/assets/{tracing-panel-BsLloPWd.js → tracing-panel-ksnvVrMH.js} +2 -2
  60. package/dist/assets/{useAddCell-cC7JUC0q.js → useAddCell-mJ1PkF-E.js} +1 -1
  61. package/dist/assets/{useCellActionButton-BiYBXHfb.js → useCellActionButton-4HWj1rt3.js} +1 -1
  62. package/dist/assets/{useDeleteCell-D2p4Dz0U.js → useDeleteCell-B0OAS0ly.js} +1 -1
  63. package/dist/assets/{useDependencyPanelTab-c5-eXlcr.js → useDependencyPanelTab-BuZ0fgAR.js} +1 -1
  64. package/dist/assets/useLifecycle-N3bfh_O1.js +1 -0
  65. package/dist/assets/useNotebookActions-DuHUqtII.js +1 -0
  66. package/dist/assets/{useRunCells-DgBY-vh9.js → useRunCells-Du76UV1R.js} +1 -1
  67. package/dist/assets/{useSplitCell-SS0kKwVk.js → useSplitCell-CeL7eJq1.js} +1 -1
  68. package/dist/index.html +25 -25
  69. package/package.json +1 -1
  70. package/src/__mocks__/common.ts +4 -4
  71. package/src/components/chat/acp/agent-panel.tsx +2 -2
  72. package/src/components/data-table/__tests__/columns.test.tsx +7 -7
  73. package/src/components/data-table/cell-hover-template/types.ts +1 -1
  74. package/src/components/data-table/cell-hover-text/types.ts +1 -1
  75. package/src/components/data-table/cell-selection/__tests__/feature.test.ts +1 -1
  76. package/src/components/data-table/cell-selection/types.ts +1 -1
  77. package/src/components/data-table/cell-styling/types.ts +1 -1
  78. package/src/components/data-table/charts/chart-spec/altair-generator.ts +2 -2
  79. package/src/components/data-table/column-formatting/types.ts +2 -2
  80. package/src/components/data-table/column-summary/legacy-chart-spec.ts +1 -1
  81. package/src/components/data-table/column-wrapping/types.ts +1 -1
  82. package/src/components/data-table/copy-column/types.ts +1 -1
  83. package/src/components/data-table/data-table.tsx +12 -12
  84. package/src/components/data-table/focus-row/types.ts +1 -1
  85. package/src/components/data-table/loading-table.tsx +1 -1
  86. package/src/components/data-table/range-focus/__tests__/atoms.test.ts +2 -2
  87. package/src/components/data-table/range-focus/atoms.ts +2 -2
  88. package/src/components/dependency-graph/dependency-graph-tree.tsx +1 -1
  89. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +1 -1
  90. package/src/components/editor/actions/pair-with-agent-modal.tsx +142 -0
  91. package/src/components/editor/actions/useNotebookActions.tsx +10 -0
  92. package/src/components/editor/ai/ai-completion-editor.tsx +1 -1
  93. package/src/components/editor/app-container.tsx +1 -1
  94. package/src/components/editor/chrome/panels/empty-state.tsx +1 -0
  95. package/src/components/editor/controls/keyboard-shortcuts.tsx +1 -1
  96. package/src/components/editor/navigation/__tests__/navigation.test.ts +1 -1
  97. package/src/components/editor/navigation/navigation.ts +1 -1
  98. package/src/components/editor/notebook-cell.tsx +1 -1
  99. package/src/components/editor/output/JsonOutput.tsx +4 -4
  100. package/src/components/editor/output/ansi-reduce.ts +2 -2
  101. package/src/components/editor/output/console/ConsoleOutput.tsx +1 -1
  102. package/src/components/editor/renderers/cells-renderer.tsx +1 -1
  103. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +1 -1
  104. package/src/components/editor/renderers/plugins.ts +1 -1
  105. package/src/components/editor/renderers/slides-layout/types.ts +2 -2
  106. package/src/components/editor/renderers/vertical-layout/__tests__/useFocusFirstEditor.test.ts +2 -2
  107. package/src/components/editor/renderers/vertical-layout/__tests__/vertical-layout.test.ts +1 -1
  108. package/src/components/find-replace/find-replace.tsx +3 -1
  109. package/src/components/forms/form.tsx +1 -1
  110. package/src/components/forms/options.ts +1 -1
  111. package/src/components/static-html/static-banner.tsx +2 -2
  112. package/src/components/terminal/terminal.tsx +4 -4
  113. package/src/components/ui/button.tsx +1 -1
  114. package/src/components/ui/command.tsx +1 -1
  115. package/src/core/ai/context/providers/__tests__/datasource.test.ts +1 -1
  116. package/src/core/ai/context/providers/__tests__/error.test.ts +1 -1
  117. package/src/core/ai/context/providers/__tests__/variable.test.ts +1 -1
  118. package/src/core/ai/context/registry.ts +2 -2
  119. package/src/core/ai/tools/registry.ts +1 -1
  120. package/src/core/cells/__tests__/cells.test.ts +2 -2
  121. package/src/core/cells/__tests__/scrollCellIntoView.test.ts +1 -1
  122. package/src/core/cells/__tests__/session.test.ts +1 -1
  123. package/src/core/cells/__tests__/utils.test.ts +1 -1
  124. package/src/core/cells/cells.ts +1 -1
  125. package/src/core/cells/ids.ts +1 -1
  126. package/src/core/codemirror/ai/request.ts +1 -1
  127. package/src/core/codemirror/copilot/__tests__/language-server.test.ts +1 -1
  128. package/src/core/codemirror/copilot/__tests__/transport.test.ts +1 -1
  129. package/src/core/codemirror/copilot/language-server.ts +1 -1
  130. package/src/core/codemirror/copilot/types.ts +1 -1
  131. package/src/core/codemirror/facet.ts +1 -1
  132. package/src/core/codemirror/language/__tests__/sql.test.ts +4 -4
  133. package/src/core/codemirror/language/languages/sql/completion-builder.ts +1 -1
  134. package/src/core/codemirror/language/metadata.ts +1 -1
  135. package/src/core/codemirror/language/types.ts +1 -1
  136. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +1 -1
  137. package/src/core/codemirror/lsp/notebook-lsp.ts +1 -1
  138. package/src/core/codemirror/misc/__tests__/dnd.test.ts +1 -1
  139. package/src/core/codemirror/rtc/loro/awareness.ts +1 -1
  140. package/src/core/config/feature-flag.tsx +1 -1
  141. package/src/core/dom/outline.ts +1 -1
  142. package/src/core/export/__tests__/hooks.test.ts +1 -1
  143. package/src/core/hotkeys/__tests__/hotkeys.test.ts +1 -1
  144. package/src/core/hotkeys/shortcuts.ts +1 -1
  145. package/src/core/islands/__tests__/bridge.test.ts +2 -2
  146. package/src/core/islands/bridge.ts +2 -2
  147. package/src/core/islands/components/output-wrapper.tsx +1 -1
  148. package/src/core/islands/parse.ts +1 -1
  149. package/src/core/lsp/__tests__/transport.test.ts +1 -1
  150. package/src/core/network/DeferredRequestRegistry.ts +1 -1
  151. package/src/core/network/__tests__/requests-network.test.ts +1 -1
  152. package/src/core/network/api.ts +2 -2
  153. package/src/core/network/requests-lazy.ts +1 -1
  154. package/src/core/network/requests-toasting.tsx +1 -1
  155. package/src/core/static/files.ts +1 -1
  156. package/src/core/vscode/vscode-bindings.ts +1 -1
  157. package/src/core/wasm/bridge.ts +3 -3
  158. package/src/core/wasm/worker/tracer.ts +1 -1
  159. package/src/core/websocket/useWebSocket.tsx +2 -2
  160. package/src/css/globals.css +37 -61
  161. package/src/custom.d.ts +1 -1
  162. package/src/hooks/__tests__/useDuplicateShortcuts.test.ts +2 -2
  163. package/src/hooks/debug.ts +3 -3
  164. package/src/hooks/useDebounce.ts +1 -1
  165. package/src/hooks/useEventListener.ts +1 -1
  166. package/src/hooks/useHotkey.ts +1 -1
  167. package/src/hooks/useLifecycle.ts +2 -2
  168. package/src/hooks/useNonce.ts +1 -1
  169. package/src/hooks/useResizeObserver.ts +2 -2
  170. package/src/main.tsx +1 -1
  171. package/src/plugins/core/RenderHTML.tsx +3 -3
  172. package/src/plugins/core/__test__/registerReactComponent.test.ts +1 -1
  173. package/src/plugins/core/registerReactComponent.tsx +4 -4
  174. package/src/plugins/core/rpc.ts +1 -1
  175. package/src/plugins/impl/DataTablePlugin.tsx +1 -1
  176. package/src/plugins/impl/FileBrowserPlugin.tsx +1 -1
  177. package/src/plugins/impl/FormPlugin.tsx +1 -1
  178. package/src/plugins/impl/__tests__/MatrixPlugin.test.tsx +1 -1
  179. package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +1 -1
  180. package/src/plugins/impl/anywidget/model.ts +1 -1
  181. package/src/plugins/impl/anywidget/types.ts +2 -2
  182. package/src/plugins/impl/anywidget/widget-binding.ts +1 -1
  183. package/src/plugins/impl/chat/ChatPlugin.tsx +1 -1
  184. package/src/plugins/impl/chat/chat-ui.tsx +1 -1
  185. package/src/plugins/impl/data-editor/glide-data-editor.tsx +1 -1
  186. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +2 -2
  187. package/src/plugins/impl/data-explorer/components/query-form.tsx +1 -1
  188. package/src/plugins/impl/data-explorer/functions/function.ts +1 -1
  189. package/src/plugins/impl/data-explorer/queries/types.ts +1 -1
  190. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -1
  191. package/src/plugins/impl/data-frames/forms/renderers.tsx +1 -1
  192. package/src/plugins/impl/data-frames/utils/operators.ts +1 -1
  193. package/src/plugins/impl/matplotlib/MatplotlibPlugin.tsx +1 -1
  194. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +1 -1
  195. package/src/plugins/impl/panel/PanelPlugin.tsx +2 -2
  196. package/src/plugins/impl/plotly/Plot.tsx +3 -3
  197. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +62 -44
  198. package/src/plugins/impl/plotly/__tests__/PlotlyPlugin.test.tsx +114 -0
  199. package/src/plugins/impl/plotly/__tests__/selection.test.ts +158 -196
  200. package/src/plugins/impl/plotly/selection.ts +274 -56
  201. package/src/plugins/impl/vega/batched.ts +1 -1
  202. package/src/plugins/impl/vega/make-selectable.ts +1 -1
  203. package/src/plugins/impl/vega/types.ts +1 -1
  204. package/src/plugins/layout/DownloadPlugin.tsx +1 -1
  205. package/src/plugins/layout/LazyPlugin.tsx +1 -1
  206. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  207. package/src/plugins/layout/mermaid/mermaid.tsx +1 -1
  208. package/src/plugins/plugins.ts +1 -1
  209. package/src/stories/data-explorer.stories.tsx +1 -1
  210. package/src/stories/dataframe.stories.tsx +1 -1
  211. package/src/stories/editor.stories.tsx +1 -1
  212. package/src/stories/select.stories.tsx +1 -1
  213. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  214. package/src/utils/Logger.ts +1 -1
  215. package/src/utils/__tests__/arrays.test.ts +1 -1
  216. package/src/utils/__tests__/blob.test.ts +1 -1
  217. package/src/utils/__tests__/dates.test.ts +1 -1
  218. package/src/utils/__tests__/errors.test.ts +1 -1
  219. package/src/utils/__tests__/objects.test.ts +3 -3
  220. package/src/utils/__tests__/waitForWs.test.ts +1 -1
  221. package/src/utils/arrays.ts +1 -1
  222. package/src/utils/assertNever.ts +1 -1
  223. package/src/utils/batch-requests.ts +2 -2
  224. package/src/utils/createReducer.ts +2 -2
  225. package/src/utils/id-tree.tsx +2 -2
  226. package/src/utils/idle.ts +1 -1
  227. package/src/utils/invariant.ts +1 -2
  228. package/src/utils/maps.ts +1 -1
  229. package/src/utils/math.ts +0 -1
  230. package/src/utils/multi-map.ts +1 -1
  231. package/src/utils/objects.ts +1 -1
  232. package/src/utils/once.ts +2 -2
  233. package/src/utils/staticImplements.ts +1 -1
  234. package/src/utils/storage/jotai.ts +1 -1
  235. package/src/utils/tracer.ts +2 -2
  236. package/dist/assets/glide-data-editor-CDqunAkw.js +0 -132
  237. package/dist/assets/index-KI45dku7.js +0 -35
  238. package/dist/assets/useLifecycle-D202VvPd.js +0 -1
  239. package/dist/assets/useNotebookActions-Bb4xxjuJ.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;
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* oxlint-disable typescript/no-explicit-any */
3
3
 
4
4
  import { tableFromIPC } from "@uwdata/flechette";
5
5
  import { batch } from "@/utils/batch-requests";
@@ -160,7 +160,7 @@ function findCommonParams(
160
160
  if (!signatureCounts.has(signature)) {
161
161
  signatureCounts.set(signature, { count: 0, param });
162
162
  }
163
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
163
+ // oxlint-disable-next-line typescript/no-non-null-assertion
164
164
  signatureCounts.get(signature)!.count++;
165
165
  }
166
166
  }
@@ -25,7 +25,7 @@ export type {
25
25
  } from "vega-lite/types_unstable/spec/unit.js";
26
26
 
27
27
  export type VegaLiteUnitSpec = TopLevelUnitSpec<Field>;
28
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ // oxlint-disable-next-line typescript/no-explicit-any
29
29
  export type GenericVegaSpec = GenericUnitSpec<any, any, any>;
30
30
  export type EncodingType = keyof Encoding<Field>;
31
31
  export type Encodings = Encoding<Field>;
@@ -35,7 +35,7 @@ interface Data {
35
35
  lazy?: boolean;
36
36
  }
37
37
 
38
- // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
38
+ // oxlint-disable-next-line typescript/consistent-type-definitions
39
39
  type Functions = {
40
40
  /**
41
41
  * Function to call for lazy loading
@@ -14,7 +14,7 @@ interface Data {
14
14
  showLoadingIndicator: boolean;
15
15
  }
16
16
 
17
- // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
17
+ // oxlint-disable-next-line typescript/consistent-type-definitions
18
18
  type PluginFunctions = {
19
19
  load: (req: {}) => Promise<{
20
20
  html: string;
@@ -69,7 +69,7 @@ const RoutesComponent = ({
69
69
  }, [handleFindMatch]);
70
70
 
71
71
  if (!matched) {
72
- // biome-ignore lint/complexity/noUselessFragments: this is intentional
72
+ // oxlint-disable-next-line react/jsx-no-useless-fragment
73
73
  return <></>;
74
74
  }
75
75
 
@@ -63,7 +63,7 @@ function randomAlpha() {
63
63
  }
64
64
 
65
65
  const Mermaid: React.FC<Props> = ({ diagram }) => {
66
- // eslint-disable-next-line react/hook-use-state
66
+ // oxlint-disable-next-line react/hook-use-state
67
67
  const [id] = useState(() => randomAlpha());
68
68
 
69
69
  const darkMode = useTheme().theme === "dark";
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* oxlint-disable typescript/no-explicit-any */
3
3
 
4
4
  import { NavigationMenuPlugin } from "@/plugins/layout/NavigationMenuPlugin";
5
5
  import { initializeUIElement } from "../core/dom/ui-element";
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- /* eslint-disable react-hooks/rules-of-hooks */
2
+ /* oxlint-disable react-hooks/rules-of-hooks */
3
3
 
4
4
  import type { Meta, StoryObj } from "@storybook/react-vite";
5
5
  import { useState } from "react";
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- /* eslint-disable react-hooks/rules-of-hooks */
2
+ /* oxlint-disable react-hooks/rules-of-hooks */
3
3
  import type { Meta, StoryObj } from "@storybook/react-vite";
4
4
  import { useState } from "react";
5
5
  import { DataFrameComponent } from "@/plugins/impl/data-frames/DataFramePlugin";
@@ -52,7 +52,7 @@ const Editor = (opts: { extensions?: Extension[] }): React.ReactNode => {
52
52
  });
53
53
 
54
54
  return () => view.destroy();
55
- // eslint-disable-next-line react-hooks/exhaustive-deps
55
+ // oxlint-disable-next-line react-hooks/exhaustive-deps
56
56
  }, [ref.current]);
57
57
 
58
58
  return <div className="cm" ref={ref} />;
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- /* eslint-disable react-hooks/rules-of-hooks */
2
+ /* oxlint-disable react-hooks/rules-of-hooks */
3
3
 
4
4
  import type { Meta, StoryObj } from "@storybook/react-vite";
5
5
  import { useState } from "react";
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- /* eslint-disable react-hooks/rules-of-hooks */
2
+ /* oxlint-disable react-hooks/rules-of-hooks */
3
3
  import type { Meta, StoryObj } from "@storybook/react-vite";
4
4
  import { useState } from "react";
5
5
  import { SwitchableMultiSelect } from "@/components/forms/switchable-multi-select";
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- /** biome-ignore-all lint/suspicious/noConsole: For console logging */
2
+ /* oxlint-disable no-console -- For console logging */
3
3
 
4
4
  import { Functions } from "./functions";
5
5
 
@@ -124,7 +124,7 @@ describe("arrays", () => {
124
124
  });
125
125
 
126
126
  it("should handle undefined/null array", () => {
127
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
127
+ // oxlint-disable-next-line typescript/no-explicit-any
128
128
  expect(arrayToggle(undefined as any, 1)).toEqual([1]);
129
129
  });
130
130
 
@@ -30,7 +30,7 @@ describe("Blob serialization and deserialization", () => {
30
30
  const serialized = await serializeBlob(testBlob);
31
31
  const deserialized = deserializeBlob(serialized);
32
32
  const reader = new FileReader();
33
- // eslint-disable-next-line unicorn/prefer-blob-reading-methods
33
+ // oxlint-disable-next-line unicorn/prefer-blob-reading-methods
34
34
  reader.readAsText(deserialized);
35
35
  await new Promise((resolve) => {
36
36
  reader.onload = () => {
@@ -73,7 +73,7 @@ describe("dates", () => {
73
73
 
74
74
  describe("with different locales", () => {
75
75
  // Save original implementation
76
- // eslint-disable-next-line @typescript-eslint/unbound-method
76
+ // oxlint-disable-next-line typescript/unbound-method
77
77
  const originalToLocaleDateString = Date.prototype.toLocaleDateString;
78
78
 
79
79
  afterAll(() => {
@@ -46,7 +46,7 @@ describe("prettyError", () => {
46
46
  });
47
47
 
48
48
  it("handles circular references", () => {
49
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ // oxlint-disable-next-line typescript/no-explicit-any
50
50
  const circular: any = { foo: "bar" };
51
51
  circular.self = circular;
52
52
  expect(prettyError(circular)).toBe("[object Object]");
@@ -29,9 +29,9 @@ describe("Objects", () => {
29
29
  });
30
30
 
31
31
  it("should return falsy input unchanged", () => {
32
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ // oxlint-disable-next-line typescript/no-explicit-any
33
33
  expect(Objects.mapValues(null as any, (v) => v)).toBe(null);
34
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ // oxlint-disable-next-line typescript/no-explicit-any
35
35
  expect(Objects.mapValues(undefined as any, (v) => v)).toBe(undefined);
36
36
  });
37
37
  });
@@ -249,7 +249,7 @@ describe("Objects", () => {
249
249
 
250
250
  it("should handle omitting non-existent keys", () => {
251
251
  const obj = { a: 1, b: 2 };
252
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
252
+ // oxlint-disable-next-line typescript/no-explicit-any
253
253
  const result = Objects.omit(obj, ["c" as any]);
254
254
  expect(result).toEqual({ a: 1, b: 2 });
255
255
  });
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* oxlint-disable typescript/no-explicit-any */
3
3
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
4
  import { waitForWs } from "../waitForWs";
5
5
 
@@ -53,7 +53,7 @@ export function arrayShallowEquals<T>(a: T[], b: T[]): boolean {
53
53
  }
54
54
 
55
55
  export const Arrays = {
56
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ // oxlint-disable-next-line typescript/no-explicit-any
57
57
  EMPTY: [] as any,
58
58
  zip: <T, U>(a: T[], b: U[]): [T, U][] => {
59
59
  invariant(a.length === b.length, "Arrays must be the same length");
@@ -15,6 +15,6 @@ export function assertNever(x: never): never {
15
15
  */
16
16
  export function logNever(x: never): void {
17
17
  Logger.warn(`Unexpected object: ${JSON.stringify(x)}`);
18
- // biome-ignore lint/correctness/noVoidTypeReturn: <explanation>
18
+ // oxlint-ignore-next-line -- noVoidTypeReturn: function returns void but value is needed for exhaustiveness
19
19
  return x;
20
20
  }
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* oxlint-disable typescript/no-explicit-any */
3
3
 
4
4
  /**
5
5
  * Debounces multiple calls to a loader function, returning the same promise for
@@ -14,7 +14,7 @@ export function batch<T, REQ extends unknown[]>(
14
14
  return (...args: REQ): Promise<T> => {
15
15
  const key = toKey(...args);
16
16
  if (requestCache.has(key)) {
17
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17
+ // oxlint-disable-next-line typescript/no-non-null-assertion
18
18
  return requestCache.get(key)!;
19
19
  }
20
20
 
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* oxlint-disable typescript/no-explicit-any */
3
3
 
4
4
  import { atom, useSetAtom } from "jotai";
5
5
  import type { Reducer } from "react";
@@ -139,7 +139,7 @@ export function createReducerAndAtoms<
139
139
  );
140
140
  }
141
141
 
142
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
142
+ // oxlint-disable-next-line typescript/no-non-null-assertion
143
143
  return actionsMap.get(setState)!;
144
144
  }
145
145
 
@@ -43,7 +43,7 @@ export class TreeNode<T> {
43
43
  const stack = [...this.children];
44
44
 
45
45
  while (stack.length > 0) {
46
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
46
+ // oxlint-disable-next-line typescript/no-non-null-assertion
47
47
  const node = stack.pop()!;
48
48
  count++;
49
49
 
@@ -62,7 +62,7 @@ export class TreeNode<T> {
62
62
  const stack = [...this.children];
63
63
 
64
64
  while (stack.length > 0) {
65
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
65
+ // oxlint-disable-next-line typescript/no-non-null-assertion
66
66
  const node = stack.pop()!;
67
67
  result.push(node.value);
68
68
 
package/src/utils/idle.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
  export function onIdle(callback: () => void) {
9
9
  if ("scheduler" in window) {
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ // oxlint-disable-next-line typescript/no-explicit-any
11
11
  (window as any).scheduler.postTask(callback, {
12
12
  priority: "background",
13
13
  });