@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
@@ -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
  });