@marimo-team/islands 0.22.1-dev8 → 0.22.1

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 (484) hide show
  1. package/dist/{ConnectedDataExplorerComponent-Dl1grr8z.js → ConnectedDataExplorerComponent-DuD8BVl6.js} +54 -54
  2. package/dist/{Plot-WhbJAbBh.js → Plot-BxlSHo0G.js} +47 -38
  3. package/dist/_basePickBy-Sow3pJjS.js +41 -0
  4. package/dist/{_baseUniq-B4eL5sTC.js → _baseUniq-C87CckHL.js} +15 -54
  5. package/dist/{any-language-editor-CT_9yBde.js → any-language-editor-BHH_pQ6M.js} +21 -21
  6. package/dist/architecture-7HQA4BMR-BHdkAMvZ.js +6 -0
  7. package/dist/{architectureDiagram-VXUJARFQ-vxgYGIMP.js → architectureDiagram-VXUJARFQ-B3YQo9At.js} +15 -15
  8. package/dist/{arrays-Du-jRBAy.js → arrays-beUWo8RF.js} +1 -1
  9. package/dist/assets/__vite-browser-external-WSlCcXn_.js +1 -0
  10. package/dist/assets/{worker-D10K3OOz.js → worker-DUYMdbtA.js} +2 -2
  11. package/dist/{blockDiagram-VD42YOAC-C7x6YTH7.js → blockDiagram-VD42YOAC-CpQ3TKEN.js} +7 -7
  12. package/dist/{button-qsiIHncQ.js → button-DNlNlZY_.js} +82 -84
  13. package/dist/{c4Diagram-YG6GDRKO-Cx4oseGg.js → c4Diagram-YG6GDRKO-CZSU4uqU.js} +4 -4
  14. package/dist/{capabilities-26mwv03y.js → capabilities-Coe9eM9T.js} +2 -2
  15. package/dist/{channel-C_50jIAn.js → channel-X3JKk8gE.js} +1 -1
  16. package/dist/{chat-ui-CtqUthFR.js → chat-ui-Cel1kBfc.js} +147 -146
  17. package/dist/{check-D_YwHEgY.js → check-CWUkiHmb.js} +1 -1
  18. package/dist/{chunk-4F5CHEZ2-Dvo_CFnR.js → chunk-4F5CHEZ2-D5mClyDv.js} +1 -1
  19. package/dist/{chunk-ABZYJK2D-D5YIs71w.js → chunk-ABZYJK2D-CZYCCtLy.js} +1 -1
  20. package/dist/{chunk-ATLVNIR6-CyOjzOcf.js → chunk-ATLVNIR6-DaOzLLgN.js} +1 -1
  21. package/dist/{chunk-B2363JML-BzZqINRO.js → chunk-B2363JML-Br0eA2T3.js} +1 -1
  22. package/dist/{chunk-B4BG7PRW-ZJeV3KdD.js → chunk-B4BG7PRW-4BjV11Br.js} +4 -4
  23. package/dist/{chunk-DI55MBZ5-Dx_wwX6l.js → chunk-DI55MBZ5-DITY3EyP.js} +4 -4
  24. package/dist/{chunk-EXTU4WIE-CgefpSXQ.js → chunk-EXTU4WIE-jUPSAk3i.js} +1 -1
  25. package/dist/{chunk-FRFDVMJY-Derq8UzY.js → chunk-FRFDVMJY-DnEvEFRR.js} +1 -1
  26. package/dist/{chunk-JA3XYJ7Z-CcIOIFpc.js → chunk-JA3XYJ7Z-BcPEfxk_.js} +2 -2
  27. package/dist/{chunk-JZLCHNYA-CgO0GG1p.js → chunk-JZLCHNYA-2bnLL3xL.js} +4 -4
  28. package/dist/{chunk-N4CR4FBY-2qzGzAxT.js → chunk-N4CR4FBY-CpZSuGSU.js} +5 -5
  29. package/dist/{chunk-PL6DKKU2-KoG71Zin.js → chunk-PL6DKKU2-DnId6G-x.js} +1 -1
  30. package/dist/{chunk-QN33PNHL-Dp1qBo28.js → chunk-QN33PNHL-B9p5ojHB.js} +1 -1
  31. package/dist/{chunk-QXUST7PY-BxmmeIwf.js → chunk-QXUST7PY-Ch6F5Obl.js} +5 -5
  32. package/dist/{chunk-S3R3BYOJ-D3Rys9ZW.js → chunk-S3R3BYOJ-B0UOFJwq.js} +3 -3
  33. package/dist/{chunk-SJTYNZTY-Co-DhKAG.js → chunk-SJTYNZTY-BsBZnJUj.js} +1 -1
  34. package/dist/{chunk-TCCFYFTB-BAhzIqBO.js → chunk-TCCFYFTB-Clbl-fTg.js} +8 -7
  35. package/dist/{chunk-TQ3KTPDO-DxYI735Z.js → chunk-TQ3KTPDO-CFkSQ30e.js} +1 -1
  36. package/dist/{chunk-TZMSLE5B-Dxumt0wv.js → chunk-TZMSLE5B-D45397J2.js} +1 -1
  37. package/dist/{chunk-UMXZTB3W-CuahpKin.js → chunk-UMXZTB3W-D-A834Bq.js} +1 -1
  38. package/dist/{classDiagram-v2-WZHVMYZB-CYoFMQKE.js → classDiagram-2ON5EDUG-C8-zE3Zv.js} +10 -10
  39. package/dist/{classDiagram-2ON5EDUG-DkOvXRlx.js → classDiagram-v2-WZHVMYZB-DrmbGANl.js} +10 -10
  40. package/dist/{clone-DDndUqI0.js → clone-DZFQCtFJ.js} +1 -1
  41. package/dist/{constants-D1Am36hX.js → constants-CvyfaCvs.js} +3 -3
  42. package/dist/{copy-CBo9JcJW.js → copy-B7781WJ3.js} +2 -2
  43. package/dist/{dagre-6UL2VRFP-BXBaU8PB.js → dagre-6UL2VRFP-OMItEBnY.js} +12 -12
  44. package/dist/{dagre-D3dlYz-r.js → dagre-QVd-lCXU.js} +13 -23
  45. package/dist/{data-grid-overlay-editor-nZux6_d2.js → data-grid-overlay-editor-lKF301ME.js} +1 -1
  46. package/dist/{diagram-PSM6KHXK-CJxjk4LG.js → diagram-PSM6KHXK-CkKbohWI.js} +16 -16
  47. package/dist/{diagram-QEK2KX5R-IMILPh_p.js → diagram-QEK2KX5R-DjUMpVcx.js} +14 -14
  48. package/dist/{diagram-S2PKOQOG-6O0g6Boj.js → diagram-S2PKOQOG-b-c0d-wZ.js} +14 -14
  49. package/dist/{dist-BkXs8bw0.js → dist--6TSlp8H.js} +1 -1
  50. package/dist/dist-7K5doRvB.js +6 -0
  51. package/dist/{dist-CQidOwep.js → dist-B43sbpd0.js} +3 -3
  52. package/dist/dist-B6I_A2-E.js +8 -0
  53. package/dist/dist-BEQsmaZY.js +5 -0
  54. package/dist/dist-BasY2RHp.js +8 -0
  55. package/dist/{dist-BemtTYzN.js → dist-Bfp1XXWt.js} +5 -5
  56. package/dist/{dist-v-1kgqZ3.js → dist-BjDuO5JW.js} +1 -1
  57. package/dist/dist-Bosc00dY.js +5 -0
  58. package/dist/{dist-DLNKBPsk.js → dist-BrxqmS9Q.js} +4 -4
  59. package/dist/{dist-DBYL08Lu.js → dist-BvhGByxL.js} +4 -4
  60. package/dist/{dist-CVqlhD3M.js → dist-C4bq5Ioy.js} +2 -2
  61. package/dist/{dist-DwmxBUOe.js → dist-CFKdzOIu.js} +2 -2
  62. package/dist/{dist-5nTQE2yt.js → dist-CIB8w0Fl.js} +2 -2
  63. package/dist/{dist-C-EcLtO9.js → dist-CNF0QBLR.js} +1 -1
  64. package/dist/dist-CQMZOn-_.js +8 -0
  65. package/dist/dist-CViQhWZ8.js +5 -0
  66. package/dist/{dist-Dg65j0em.js → dist-CcMfr7jD.js} +1 -1
  67. package/dist/{dist-C0XYIHKJ.js → dist-Ci0CXEFt.js} +1 -1
  68. package/dist/dist-Cz6rLfwY.js +5 -0
  69. package/dist/dist-D8eq8st3.js +6 -0
  70. package/dist/{dist-qoCY8giM.js → dist-DAfcmt-d.js} +2 -2
  71. package/dist/{dist-B6Op2ogv.js → dist-DD_cYHOl.js} +2 -2
  72. package/dist/{dist-BUSLKXcu.js → dist-DFK94vuS.js} +2 -2
  73. package/dist/{dist-DBXPlQ0D.js → dist-DGNtjMZu.js} +1 -1
  74. package/dist/{dist-C9qF7MRB.js → dist-DJ9F1eHs.js} +2 -2
  75. package/dist/{dist-Ci_jEudG.js → dist-DJKubHDd.js} +1 -1
  76. package/dist/{dist-CnFp2Kcl.js → dist-DLafRu9s.js} +2 -2
  77. package/dist/dist-DM1UDXdl.js +5 -0
  78. package/dist/dist-DNrtWPgS.js +5 -0
  79. package/dist/dist-D_UjpfOY.js +1381 -0
  80. package/dist/{dist-DmFS6KZW.js → dist-DbnBiLNH.js} +3 -3
  81. package/dist/{dist-BiZZAo22.js → dist-DlSUOIm9.js} +1 -1
  82. package/dist/{dist-DStU8He1.js → dist-Doy0mQDg.js} +2 -2
  83. package/dist/{dist-CxAX99oC.js → dist-DpkJHKB8.js} +2 -2
  84. package/dist/{dist-DjaZNkZ7.js → dist-LhQNUe5A.js} +3 -3
  85. package/dist/dist-V7q2qnpA.js +5 -0
  86. package/dist/{dist-BJ3fhRYu.js → dist-VqF3W_ue.js} +2 -2
  87. package/dist/dist-a5_hPgu2.js +8 -0
  88. package/dist/{dist-B9KLrfoh.js → dist-ej6AQKaS.js} +1 -1
  89. package/dist/{dist-Brb6VNc4.js → dist-m9tsXsFf.js} +2 -2
  90. package/dist/{dist-CBwMSFDu.js → dist-tGk0aZ--.js} +2 -2
  91. package/dist/dist-uVyZcV1-.js +5 -0
  92. package/dist/{erDiagram-Q2GNP2WA-sho7Cl9f.js → erDiagram-Q2GNP2WA-CDhLaOZ1.js} +10 -10
  93. package/dist/{error-banner-Bx9kIgrs.js → error-banner-Cjf0RU9I.js} +79 -79
  94. package/dist/{esm-CMg2ABu6.js → esm-4wmsH2lp.js} +6 -6
  95. package/dist/{esm-cqK9POGH.js → esm-CD1iby2n.js} +23 -23
  96. package/dist/{flowDiagram-NV44I4VS-C4nY4Fbz.js → flowDiagram-NV44I4VS-BDi4O4CL.js} +10 -10
  97. package/dist/{ganttDiagram-JELNMOA3-CtxNcCM2.js → ganttDiagram-JELNMOA3-BpZE6kVp.js} +3 -3
  98. package/dist/{gitGraph-G5XIXVHT-SL6TDof6.js → gitGraph-G5XIXVHT-B_c6xFJv.js} +3 -3
  99. package/dist/{gitGraphDiagram-V2S2FVAM-D9885mxd.js → gitGraphDiagram-V2S2FVAM-iQnXzbPM.js} +13 -13
  100. package/dist/{glide-data-editor-CkVEV-Gk.js → glide-data-editor-BqnvTmDo.js} +1739 -1708
  101. package/dist/{graphlib-CxWdvYQt.js → graphlib-BV1_gi0C.js} +4 -3
  102. package/dist/hasIn-DnfJcYpY.js +108 -0
  103. package/dist/{info-VBDWY6EO-6MXPTSmi.js → info-VBDWY6EO-BTyzxmhr.js} +3 -3
  104. package/dist/{infoDiagram-HS3SLOUP-Bw2FlRwF.js → infoDiagram-HS3SLOUP-OYrX6uO3.js} +13 -13
  105. package/dist/{input-BSde8uV4.js → input-CFY9gApZ.js} +5055 -5055
  106. package/dist/{isEmpty-BQtUinxJ.js → isEmpty-B7FX9wKt.js} +1 -1
  107. package/dist/{isSymbol-DFp8040B.js → isSymbol-DCbjQG_U.js} +1 -1
  108. package/dist/{journeyDiagram-XKPGCS4Q-BXlCEth8.js → journeyDiagram-XKPGCS4Q-ClPC94aN.js} +3 -3
  109. package/dist/{kanban-definition-3W4ZIXB7-CorxzSYm.js → kanban-definition-3W4ZIXB7-DHEAKdZt.js} +7 -7
  110. package/dist/{label-DTNqw9tv.js → label-DbZGAoCH.js} +538 -569
  111. package/dist/{loader-3c9hT4kT.js → loader-Bd1kgLn7.js} +19 -16
  112. package/dist/main.js +35011 -34898
  113. package/dist/{memoize-CuHciEBb.js → memoize-CSTI9eOX.js} +1 -1
  114. package/dist/{merge-CA_buyY3.js → merge-CVhG7q_o.js} +1 -1
  115. package/dist/{mermaid-CEKslOkI.js → mermaid-B2HDLx2g.js} +54 -54
  116. package/dist/{mermaid-parser.core-cq4YDee-.js → mermaid-parser.core-ntCgyx0x.js} +8 -8
  117. package/dist/min-Ds3gG0Ff.js +96 -0
  118. package/dist/{mindmap-definition-VGOIOE7T-DRsT8UaN.js → mindmap-definition-VGOIOE7T-CxEUZZvY.js} +9 -9
  119. package/dist/{now-CXAdKY5k.js → now-nrrrOr01.js} +1 -1
  120. package/dist/{once-CZno0h-b.js → once-C_TIu-kR.js} +1 -1
  121. package/dist/{packet-DYOGHKS2-Dw08gMaZ.js → packet-DYOGHKS2-BhvnpoGi.js} +3 -3
  122. package/dist/{pie-VRWISCQL-C5SPSvT8.js → pie-VRWISCQL-dILuA3iG.js} +3 -3
  123. package/dist/{pieDiagram-ADFJNKIX-DhJ1Cx2O.js → pieDiagram-ADFJNKIX-U3LrUqAS.js} +14 -14
  124. package/dist/{process-output-KyzWazB-.js → process-output-DC1TOnIl.js} +3184 -3207
  125. package/dist/{quadrantDiagram-AYHSOK5B-DXUFIWlz.js → quadrantDiagram-AYHSOK5B-BVWuq-3R.js} +2 -2
  126. package/dist/{radar-ZZBFDIW7-BvY0bgSg.js → radar-ZZBFDIW7-DwFrOJDj.js} +3 -3
  127. package/dist/range-fJeId9Ri.js +30 -0
  128. package/dist/{requirementDiagram-UZGBJVZJ-DO_gtQIb.js → requirementDiagram-UZGBJVZJ-D0zpQnKC.js} +9 -9
  129. package/dist/{sankeyDiagram-TZEHDZUN-OZzXEkuG.js → sankeyDiagram-TZEHDZUN-CExy1joT.js} +2 -2
  130. package/dist/{sequenceDiagram-WL72ISMW-K7nZRifV.js → sequenceDiagram-WL72ISMW-D1BJxLjH.js} +4 -4
  131. package/dist/{slides-component-CIcSvFh7.js → slides-component-CX2JC-Ws.js} +2 -2
  132. package/dist/{spec-DYaR1rJh.js → spec-CD7QaCV-.js} +3 -3
  133. package/dist/{stateDiagram-FKZM4ZOC-DzXJZAq7.js → stateDiagram-FKZM4ZOC-B1S8jGMn.js} +12 -12
  134. package/dist/{stateDiagram-v2-4FDKWEC3-BZBPUmyF.js → stateDiagram-v2-4FDKWEC3-BH5ozUbc.js} +10 -10
  135. package/dist/stex-CQDv3aS8.js +4 -0
  136. package/dist/style.css +1 -1
  137. package/dist/{timeline-definition-IT6M3QCI-DNoLAh-i.js → timeline-definition-IT6M3QCI-BDT9JAmn.js} +2 -2
  138. package/dist/{toDate-D6VXexnV.js → toDate-CUqpEbBS.js} +5 -5
  139. package/dist/{toNumber-xFPoy1OI.js → toNumber-55tjPCWr.js} +2 -2
  140. package/dist/tooltip-BXEpXV3R.js +404 -0
  141. package/dist/{treemap-GDKQZRPO-C5OoxpmV.js → treemap-GDKQZRPO-bx2ngsgN.js} +3 -3
  142. package/dist/{types-CQ-RbYxp.js → types-D_ntCXg0.js} +3 -3
  143. package/dist/{useAsyncData-Cd4Urlww.js → useAsyncData-rN1nzPaS.js} +2 -2
  144. package/dist/{useDeepCompareMemoize-X7clcrcQ.js → useDeepCompareMemoize-Ch-7Rk2x.js} +4 -4
  145. package/dist/{useIframeCapabilities-BVQrlRBd.js → useIframeCapabilities-CqhrVue6.js} +1 -1
  146. package/dist/useLifecycle-4fA1pHoh.js +177 -0
  147. package/dist/{useTheme-Dm1WaAGy.js → useTheme-MWfxn4oz.js} +4 -5
  148. package/dist/{vega-component-A6unyUJS.js → vega-component-CPhNLfZZ.js} +25 -25
  149. package/dist/{xychartDiagram-PRI3JC2R-ehVeySMW.js → xychartDiagram-PRI3JC2R-CuAZiqHS.js} +5 -5
  150. package/dist/{Combination-B--d1_LV.js → zod-C6UGQ3fz.js} +8085 -8151
  151. package/package.json +8 -41
  152. package/src/__mocks__/common.ts +4 -4
  153. package/src/__tests__/main.test.tsx +12 -14
  154. package/src/components/ai/ai-provider-icon.tsx +3 -2
  155. package/src/components/app-config/user-config-form.tsx +0 -27
  156. package/src/components/chat/acp/agent-docs.tsx +3 -3
  157. package/src/components/chat/acp/agent-panel.tsx +71 -24
  158. package/src/components/chat/acp/agent-selector.tsx +2 -11
  159. package/src/components/chat/acp/state.ts +15 -3
  160. package/src/components/chat/chat-history-popover.tsx +1 -1
  161. package/src/components/chat/chat-panel.tsx +2 -1
  162. package/src/components/data-table/TableBottomBar.tsx +7 -1
  163. package/src/components/data-table/TableTopBar.tsx +31 -35
  164. package/src/components/data-table/__tests__/columns.test.tsx +7 -7
  165. package/src/components/data-table/cell-hover-template/types.ts +1 -1
  166. package/src/components/data-table/cell-hover-text/types.ts +1 -1
  167. package/src/components/data-table/cell-selection/__tests__/feature.test.ts +1 -1
  168. package/src/components/data-table/cell-selection/types.ts +4 -3
  169. package/src/components/data-table/cell-styling/types.ts +1 -1
  170. package/src/components/data-table/charts/chart-spec/altair-generator.ts +2 -2
  171. package/src/components/data-table/charts/charts.tsx +2 -2
  172. package/src/components/data-table/charts/components/chart-items.tsx +1 -1
  173. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  174. package/src/components/data-table/column-formatting/types.ts +5 -4
  175. package/src/components/data-table/column-header.tsx +4 -2
  176. package/src/components/data-table/column-summary/legacy-chart-spec.ts +1 -1
  177. package/src/components/data-table/column-wrapping/types.ts +4 -3
  178. package/src/components/data-table/columns.tsx +4 -1
  179. package/src/components/data-table/copy-column/types.ts +4 -3
  180. package/src/components/data-table/data-table.tsx +12 -11
  181. package/src/components/data-table/focus-row/types.ts +4 -3
  182. package/src/components/data-table/loading-table.tsx +1 -1
  183. package/src/components/data-table/pagination.tsx +1 -1
  184. package/src/components/data-table/range-focus/__tests__/atoms.test.ts +13 -13
  185. package/src/components/data-table/range-focus/__tests__/use-cell-range-selection.test.ts +9 -11
  186. package/src/components/data-table/range-focus/atoms.ts +2 -2
  187. package/src/components/data-table/range-focus/cell-selection-stats.tsx +3 -1
  188. package/src/components/data-table/renderers.tsx +18 -12
  189. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +1 -1
  190. package/src/components/data-table/table-explorer-panel/table-explorer-panel.tsx +2 -2
  191. package/src/components/dependency-graph/dependency-graph-tree.tsx +1 -1
  192. package/src/components/editor/__tests__/data-attributes.test.tsx +93 -94
  193. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +1 -1
  194. package/src/components/editor/actions/name-cell-input.tsx +4 -2
  195. package/src/components/editor/actions/pair-with-agent-modal.tsx +179 -0
  196. package/src/components/editor/actions/useCellActionButton.tsx +4 -2
  197. package/src/components/editor/actions/useNotebookActions.tsx +10 -0
  198. package/src/components/editor/ai/add-cell-with-ai.tsx +2 -1
  199. package/src/components/editor/ai/ai-completion-editor.tsx +1 -1
  200. package/src/components/editor/app-container.tsx +1 -1
  201. package/src/components/editor/cell/CellStatus.tsx +4 -5
  202. package/src/components/editor/cell/cell-context-menu.tsx +4 -2
  203. package/src/components/editor/cell/code/cell-editor.tsx +3 -2
  204. package/src/components/editor/cell/toolbar.tsx +2 -1
  205. package/src/components/editor/chrome/components/contribute-snippet-button.tsx +4 -1
  206. package/src/components/editor/chrome/components/feedback-button.tsx +4 -1
  207. package/src/components/editor/chrome/panels/context-aware-panel/context-aware-panel.tsx +1 -1
  208. package/src/components/editor/chrome/panels/empty-state.tsx +1 -0
  209. package/src/components/editor/chrome/panels/file-explorer-panel.tsx +0 -10
  210. package/src/components/editor/chrome/panels/snippets-panel.tsx +1 -1
  211. package/src/components/editor/chrome/panels/write-secret-modal.tsx +1 -1
  212. package/src/components/editor/chrome/wrapper/app-chrome.tsx +4 -1
  213. package/src/components/editor/chrome/wrapper/footer-items/lsp-status.tsx +2 -1
  214. package/src/components/editor/chrome/wrapper/storage.ts +2 -2
  215. package/src/components/editor/connections/database/as-code.ts +1 -1
  216. package/src/components/editor/connections/form-renderers.tsx +1 -1
  217. package/src/components/editor/connections/storage/as-code.ts +1 -1
  218. package/src/components/editor/controls/keyboard-shortcuts.tsx +1 -1
  219. package/src/components/editor/header/filename-input.tsx +4 -1
  220. package/src/components/editor/links/cell-link-list.tsx +1 -1
  221. package/src/components/editor/navigation/__tests__/navigation.test.ts +1 -1
  222. package/src/components/editor/navigation/clipboard.ts +1 -1
  223. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +2 -2
  224. package/src/components/editor/navigation/navigation.ts +3 -3
  225. package/src/components/editor/notebook-cell.tsx +1 -1
  226. package/src/components/editor/output/JsonOutput.tsx +6 -6
  227. package/src/components/editor/output/ansi-reduce.ts +2 -2
  228. package/src/components/editor/output/console/ConsoleOutput.tsx +2 -2
  229. package/src/components/editor/renderers/cells-renderer.tsx +1 -1
  230. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +1 -1
  231. package/src/components/editor/renderers/plugins.ts +1 -1
  232. package/src/components/editor/renderers/slides-layout/types.ts +2 -2
  233. package/src/components/editor/renderers/vertical-layout/__tests__/useFocusFirstEditor.test.ts +2 -2
  234. package/src/components/editor/renderers/vertical-layout/__tests__/vertical-layout.test.ts +1 -1
  235. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +12 -13
  236. package/src/components/find-replace/find-replace.tsx +3 -1
  237. package/src/components/forms/form.tsx +1 -1
  238. package/src/components/forms/options.ts +1 -1
  239. package/src/components/pages/gallery-page.tsx +1 -1
  240. package/src/components/static-html/static-banner.tsx +2 -2
  241. package/src/components/storage/__tests__/storage-snippets.test.ts +4 -6
  242. package/src/components/terminal/terminal.tsx +4 -4
  243. package/src/components/tracing/tracing.test.tsx +30 -30
  244. package/src/components/ui/accordion.tsx +1 -1
  245. package/src/components/ui/alert-dialog.tsx +1 -1
  246. package/src/components/ui/badge.tsx +2 -1
  247. package/src/components/ui/button.tsx +5 -4
  248. package/src/components/ui/calendar.tsx +3 -2
  249. package/src/components/ui/checkbox.tsx +1 -1
  250. package/src/components/ui/combobox.tsx +2 -1
  251. package/src/components/ui/command.tsx +5 -2
  252. package/src/components/ui/context-menu.tsx +1 -1
  253. package/src/components/ui/date-input.tsx +7 -6
  254. package/src/components/ui/date-picker.tsx +6 -4
  255. package/src/components/ui/dialog.tsx +1 -1
  256. package/src/components/ui/draggable-popover.tsx +1 -1
  257. package/src/components/ui/dropdown-menu.tsx +2 -1
  258. package/src/components/ui/field.tsx +1 -2
  259. package/src/components/ui/form.tsx +4 -5
  260. package/src/components/ui/fullscreen.tsx +4 -1
  261. package/src/components/ui/label.tsx +1 -1
  262. package/src/components/ui/navigation.tsx +1 -1
  263. package/src/components/ui/popover.tsx +1 -1
  264. package/src/components/ui/progress.tsx +4 -3
  265. package/src/components/ui/query-param-preserving-link.tsx +4 -2
  266. package/src/components/ui/radio-group.tsx +1 -1
  267. package/src/components/ui/range-slider.tsx +1 -1
  268. package/src/components/ui/scroll-area.tsx +1 -1
  269. package/src/components/ui/select.tsx +1 -1
  270. package/src/components/ui/sheet.tsx +3 -2
  271. package/src/components/ui/slider.tsx +1 -1
  272. package/src/components/ui/switch.tsx +1 -1
  273. package/src/components/ui/tabs.tsx +1 -1
  274. package/src/components/ui/textarea.tsx +1 -2
  275. package/src/components/ui/toast.tsx +1 -1
  276. package/src/components/ui/toggle.tsx +1 -1
  277. package/src/components/ui/tooltip.tsx +1 -1
  278. package/src/components/variables/variables-table.tsx +1 -1
  279. package/src/core/ai/context/providers/__tests__/datasource.test.ts +1 -1
  280. package/src/core/ai/context/providers/__tests__/error.test.ts +1 -1
  281. package/src/core/ai/context/providers/__tests__/variable.test.ts +1 -1
  282. package/src/core/ai/context/providers/cell-output.ts +1 -2
  283. package/src/core/ai/context/registry.ts +2 -2
  284. package/src/core/ai/tools/edit-notebook-tool.ts +4 -3
  285. package/src/core/ai/tools/registry.ts +1 -1
  286. package/src/core/ai/tools/run-cells-tool.ts +4 -3
  287. package/src/core/cells/__tests__/add-missing-import.test.ts +23 -22
  288. package/src/core/cells/__tests__/cell.test.ts +14 -13
  289. package/src/core/cells/__tests__/cells.test.ts +2 -2
  290. package/src/core/cells/__tests__/scrollCellIntoView.test.ts +1 -1
  291. package/src/core/cells/__tests__/session.test.ts +1 -1
  292. package/src/core/cells/__tests__/utils.test.ts +1 -1
  293. package/src/core/cells/cells.ts +2 -2
  294. package/src/core/cells/ids.ts +1 -1
  295. package/src/core/cells/logs.ts +1 -1
  296. package/src/core/codemirror/ai/request.ts +1 -1
  297. package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -17
  298. package/src/core/codemirror/cells/traceback-decorations.ts +1 -1
  299. package/src/core/codemirror/copilot/__tests__/language-server.test.ts +1 -1
  300. package/src/core/codemirror/copilot/__tests__/transport.test.ts +129 -3
  301. package/src/core/codemirror/copilot/client.ts +9 -2
  302. package/src/core/codemirror/copilot/getCodes.ts +1 -1
  303. package/src/core/codemirror/copilot/language-server.ts +12 -1
  304. package/src/core/codemirror/copilot/transport.ts +34 -8
  305. package/src/core/codemirror/copilot/types.ts +1 -1
  306. package/src/core/codemirror/facet.ts +1 -1
  307. package/src/core/codemirror/language/__tests__/sql.test.ts +4 -4
  308. package/src/core/codemirror/language/languages/markdown.ts +1 -3
  309. package/src/core/codemirror/language/languages/python.ts +4 -0
  310. package/src/core/codemirror/language/languages/sql/completion-builder.ts +1 -1
  311. package/src/core/codemirror/language/languages/sql/completion-sources.tsx +4 -6
  312. package/src/core/codemirror/language/languages/sql/sql.ts +1 -3
  313. package/src/core/codemirror/language/metadata.ts +1 -1
  314. package/src/core/codemirror/language/panel/sql.tsx +4 -1
  315. package/src/core/codemirror/language/types.ts +1 -1
  316. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +1 -1
  317. package/src/core/codemirror/lsp/notebook-lsp.ts +1 -1
  318. package/src/core/codemirror/misc/__tests__/dnd.test.ts +1 -1
  319. package/src/core/codemirror/reactive-references/__tests__/analyzer.test.ts +28 -42
  320. package/src/core/codemirror/rtc/loro/awareness.ts +1 -1
  321. package/src/core/config/__tests__/config-schema.test.ts +2 -6
  322. package/src/core/config/config-schema.ts +0 -1
  323. package/src/core/config/feature-flag.tsx +1 -3
  324. package/src/core/datasets/data-source-connections.ts +4 -2
  325. package/src/core/datasets/state.ts +1 -1
  326. package/src/core/dom/__tests__/htmlUtils.test.ts +8 -14
  327. package/src/core/dom/__tests__/outline.test.ts +2 -3
  328. package/src/core/dom/outline.ts +1 -1
  329. package/src/core/dom/uiregistry.ts +42 -8
  330. package/src/core/edit-app.tsx +4 -1
  331. package/src/core/export/__tests__/hooks.test.ts +1 -1
  332. package/src/core/hotkeys/__tests__/hotkeys.test.ts +1 -1
  333. package/src/core/hotkeys/shortcuts.ts +1 -1
  334. package/src/core/islands/__tests__/bridge.test.ts +2 -2
  335. package/src/core/islands/__tests__/parse.test.ts +8 -7
  336. package/src/core/islands/bridge.ts +2 -2
  337. package/src/core/islands/components/output-wrapper.tsx +1 -1
  338. package/src/core/islands/parse.ts +1 -1
  339. package/src/core/lsp/__tests__/transport.test.ts +1 -1
  340. package/src/core/network/CachingRequestRegistry.ts +1 -1
  341. package/src/core/network/DeferredRequestRegistry.ts +1 -1
  342. package/src/core/network/__tests__/requests-network.test.ts +1 -1
  343. package/src/core/network/api.ts +2 -2
  344. package/src/core/network/requests-lazy.ts +1 -1
  345. package/src/core/network/requests-toasting.tsx +1 -1
  346. package/src/core/runtime/runtime.ts +1 -0
  347. package/src/core/saving/__tests__/filename.test.ts +7 -6
  348. package/src/core/static/__tests__/download-html.test.ts +16 -15
  349. package/src/core/static/__tests__/files.test.ts +30 -28
  350. package/src/core/static/files.ts +1 -1
  351. package/src/core/vscode/vscode-bindings.ts +1 -1
  352. package/src/core/wasm/bridge.ts +3 -3
  353. package/src/core/wasm/worker/tracer.ts +1 -1
  354. package/src/core/websocket/useWebSocket.tsx +5 -3
  355. package/src/css/app/Cell.css +5 -2
  356. package/src/css/globals.css +18 -16
  357. package/src/custom.d.ts +1 -1
  358. package/src/hooks/__tests__/useDuplicateShortcuts.test.ts +3 -3
  359. package/src/hooks/debug.ts +3 -3
  360. package/src/hooks/useDebounce.ts +1 -1
  361. package/src/hooks/useEventListener.ts +1 -1
  362. package/src/hooks/useHotkey.ts +1 -1
  363. package/src/hooks/useLifecycle.ts +2 -2
  364. package/src/hooks/useNonce.ts +1 -1
  365. package/src/hooks/useResizeObserver.ts +2 -2
  366. package/src/main.tsx +1 -1
  367. package/src/plugins/core/BadPlugin.tsx +7 -6
  368. package/src/plugins/core/RenderHTML.tsx +3 -3
  369. package/src/plugins/core/__test__/registerReactComponent.test.ts +1 -1
  370. package/src/plugins/core/registerReactComponent.tsx +4 -4
  371. package/src/plugins/core/rpc.ts +1 -1
  372. package/src/plugins/impl/CheckboxPlugin.tsx +4 -1
  373. package/src/plugins/impl/DataEditorPlugin.tsx +8 -3
  374. package/src/plugins/impl/DataTablePlugin.tsx +5 -2
  375. package/src/plugins/impl/FileBrowserPlugin.tsx +1 -1
  376. package/src/plugins/impl/FormPlugin.tsx +2 -3
  377. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +4 -1
  378. package/src/plugins/impl/__tests__/MatrixPlugin.test.tsx +1 -1
  379. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +43 -15
  380. package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +3 -12
  381. package/src/plugins/impl/anywidget/__tests__/model.test.ts +2 -2
  382. package/src/plugins/impl/anywidget/model.ts +1 -1
  383. package/src/plugins/impl/anywidget/types.ts +2 -2
  384. package/src/plugins/impl/anywidget/widget-binding.ts +1 -1
  385. package/src/plugins/impl/chat/ChatPlugin.tsx +1 -1
  386. package/src/plugins/impl/chat/chat-ui.tsx +5 -2
  387. package/src/plugins/impl/data-editor/glide-data-editor.tsx +2 -2
  388. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +2 -2
  389. package/src/plugins/impl/data-explorer/components/query-form.tsx +1 -1
  390. package/src/plugins/impl/data-explorer/functions/function.ts +1 -1
  391. package/src/plugins/impl/data-explorer/queries/types.ts +1 -1
  392. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -1
  393. package/src/plugins/impl/data-frames/forms/__tests__/form.test.tsx +7 -9
  394. package/src/plugins/impl/data-frames/forms/renderers.tsx +1 -1
  395. package/src/plugins/impl/data-frames/utils/operators.ts +1 -1
  396. package/src/plugins/impl/matplotlib/MatplotlibPlugin.tsx +1 -1
  397. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +1 -1
  398. package/src/plugins/impl/panel/PanelPlugin.tsx +2 -2
  399. package/src/plugins/impl/plotly/Plot.tsx +3 -3
  400. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +65 -103
  401. package/src/plugins/impl/plotly/__tests__/PlotlyPlugin.test.tsx +114 -0
  402. package/src/plugins/impl/plotly/__tests__/selection.test.ts +199 -0
  403. package/src/plugins/impl/plotly/selection.ts +333 -0
  404. package/src/plugins/impl/vega/__tests__/make-selectable.test.ts +13 -14
  405. package/src/plugins/impl/vega/__tests__/utils.test.ts +68 -0
  406. package/src/plugins/impl/vega/batched.ts +1 -1
  407. package/src/plugins/impl/vega/make-selectable.ts +3 -3
  408. package/src/plugins/impl/vega/types.ts +1 -1
  409. package/src/plugins/impl/vega/utils.ts +14 -5
  410. package/src/plugins/impl/vega/vega.css +2 -1
  411. package/src/plugins/layout/DownloadPlugin.tsx +1 -1
  412. package/src/plugins/layout/ImageComparisonPlugin.tsx +1 -3
  413. package/src/plugins/layout/LazyPlugin.tsx +1 -1
  414. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  415. package/src/plugins/layout/mermaid/mermaid.tsx +1 -1
  416. package/src/plugins/plugins.ts +1 -1
  417. package/src/plugins/stateless-plugin.ts +4 -2
  418. package/src/stories/data-explorer.stories.tsx +1 -1
  419. package/src/stories/dataframe.stories.tsx +1 -1
  420. package/src/stories/editor.stories.tsx +1 -1
  421. package/src/stories/select.stories.tsx +1 -1
  422. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  423. package/src/utils/Logger.ts +1 -1
  424. package/src/utils/__tests__/arrays.test.ts +1 -1
  425. package/src/utils/__tests__/blob.test.ts +1 -1
  426. package/src/utils/__tests__/cell-urls.test.ts +24 -21
  427. package/src/utils/__tests__/dates.test.ts +1 -1
  428. package/src/utils/__tests__/errors.test.ts +1 -1
  429. package/src/utils/__tests__/filenames.test.ts +15 -14
  430. package/src/utils/__tests__/json-parser.test.ts +14 -21
  431. package/src/utils/__tests__/multi-map.test.ts +3 -3
  432. package/src/utils/__tests__/objects.test.ts +3 -3
  433. package/src/utils/__tests__/path.test.ts +34 -31
  434. package/src/utils/__tests__/urls.test.ts +19 -18
  435. package/src/utils/__tests__/versions.test.ts +5 -5
  436. package/src/utils/__tests__/waitForWs.test.ts +1 -1
  437. package/src/utils/arrays.ts +2 -2
  438. package/src/utils/assertNever.ts +1 -1
  439. package/src/utils/batch-requests.ts +2 -2
  440. package/src/utils/createReducer.ts +2 -2
  441. package/src/utils/id-tree.tsx +2 -2
  442. package/src/utils/idle.ts +1 -1
  443. package/src/utils/invariant.ts +1 -2
  444. package/src/utils/json/base64.ts +2 -5
  445. package/src/utils/maps.ts +1 -1
  446. package/src/utils/math.ts +0 -1
  447. package/src/utils/mime-types.ts +1 -1
  448. package/src/utils/multi-map.ts +1 -1
  449. package/src/utils/objects.ts +1 -1
  450. package/src/utils/once.ts +2 -2
  451. package/src/utils/staticImplements.ts +1 -1
  452. package/src/utils/storage/jotai.ts +1 -1
  453. package/src/utils/tracer.ts +3 -2
  454. package/dist/_basePickBy-QjOmBDRE.js +0 -110
  455. package/dist/_baseSet-xgn1IbGV.js +0 -27
  456. package/dist/architecture-7HQA4BMR-BRyVh_Za.js +0 -6
  457. package/dist/assets/__vite-browser-external-Us1ds95c.js +0 -1
  458. package/dist/dist-B0R_ZM4-.js +0 -6
  459. package/dist/dist-B4a9_9pj.js +0 -5
  460. package/dist/dist-BCSUKEwO.js +0 -5
  461. package/dist/dist-BONIDQq6.js +0 -5
  462. package/dist/dist-BYeRx2hb.js +0 -5
  463. package/dist/dist-D2Rk1j4R.js +0 -1381
  464. package/dist/dist-DZjX5TYv.js +0 -5
  465. package/dist/dist-Dkw9x6kc.js +0 -5
  466. package/dist/dist-Ds6UaXGR.js +0 -6
  467. package/dist/dist-KuEJ1Q53.js +0 -8
  468. package/dist/dist-S72WNyTZ.js +0 -5
  469. package/dist/dist-bTG-yssT.js +0 -5
  470. package/dist/dist-diF0sguc.js +0 -8
  471. package/dist/dist-mJ84BIgu.js +0 -8
  472. package/dist/dist-wSIhFWQz.js +0 -8
  473. package/dist/get-CqrzlV1v.js +0 -68
  474. package/dist/range-CYz5jI--.js +0 -17
  475. package/dist/stex-CZyTRGVB.js +0 -4
  476. package/dist/tooltip-DGHTbHl5.js +0 -404
  477. package/dist/useLifecycle-Dids8BPm.js +0 -173
  478. /package/dist/{dist-KZI_BHqV.js → dist-CxZvoNao.js} +0 -0
  479. /package/dist/{invariant-D4hPsZFI.js → invariant-e8eBgdux.js} +0 -0
  480. /package/dist/{isArrayLikeObject-C-hFPChh.js → isArrayLikeObject-LXbTYiBa.js} +0 -0
  481. /package/dist/{main-CvkAPtaq.js → main-XimWhSi_.js} +0 -0
  482. /package/dist/{purify.es-ukiMXY-F.js → purify.es-hTCfRGdl.js} +0 -0
  483. /package/dist/{react-dom-BKwCWYPW.js → react-dom-BSUuJjCR.js} +0 -0
  484. /package/dist/{stex-Ze8D4R_5.js → stex-D887Ylhf.js} +0 -0
@@ -0,0 +1,114 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { act, render, waitFor } from "@testing-library/react";
4
+ import { Suspense } from "react";
5
+ import { describe, expect, it, vi } from "vitest";
6
+ import { SetupMocks } from "@/__mocks__/common";
7
+ import type { Setter } from "@/plugins/types";
8
+ import { PlotlyComponent } from "../PlotlyPlugin";
9
+
10
+ SetupMocks.resizeObserver();
11
+
12
+ type CapturedPlotProps = {
13
+ onClick?: (event: {
14
+ points: {
15
+ data?: { type?: string };
16
+ x?: string | number;
17
+ y?: string | number;
18
+ pointIndex?: number;
19
+ pointNumber?: number;
20
+ curveNumber?: number;
21
+ }[];
22
+ }) => void;
23
+ } | null;
24
+
25
+ let capturedPlotProps: CapturedPlotProps = null;
26
+
27
+ vi.mock("../Plot", () => ({
28
+ Plot: (props: CapturedPlotProps) => {
29
+ capturedPlotProps = props;
30
+ return <div data-testid="plotly-mock" />;
31
+ },
32
+ }));
33
+
34
+ vi.mock("../usePlotlyLayout", () => ({
35
+ usePlotlyLayout: ({
36
+ originalFigure,
37
+ }: {
38
+ originalFigure: {
39
+ data: unknown[];
40
+ layout: Record<string, unknown>;
41
+ frames: unknown[] | null;
42
+ };
43
+ }) => ({
44
+ figure: originalFigure,
45
+ layout: originalFigure.layout,
46
+ handleReset: vi.fn(),
47
+ }),
48
+ }));
49
+
50
+ vi.mock("@/hooks/useScript", () => ({
51
+ useScript: () => "ready",
52
+ }));
53
+
54
+ vi.mock("react-use-event-hook", () => ({
55
+ default: <T,>(callback: T) => callback,
56
+ }));
57
+
58
+ describe("PlotlyPlugin", () => {
59
+ it("clicking a bar selects that bar", async () => {
60
+ const setValue = vi.fn<Setter<unknown>>();
61
+
62
+ render(
63
+ <Suspense fallback={null}>
64
+ <PlotlyComponent
65
+ figure={{
66
+ data: [{ type: "bar" }],
67
+ layout: {},
68
+ frames: null,
69
+ }}
70
+ value={undefined}
71
+ setValue={setValue}
72
+ host={document.createElement("div")}
73
+ config={{}}
74
+ />
75
+ </Suspense>,
76
+ );
77
+
78
+ await waitFor(() => {
79
+ expect(capturedPlotProps).not.toBeNull();
80
+ });
81
+
82
+ act(() => {
83
+ capturedPlotProps?.onClick?.({
84
+ points: [
85
+ {
86
+ data: { type: "bar" },
87
+ x: "Feb",
88
+ y: 18,
89
+ pointIndex: 1,
90
+ pointNumber: 1,
91
+ curveNumber: 0,
92
+ },
93
+ ],
94
+ });
95
+ });
96
+
97
+ expect(setValue).toHaveBeenCalledTimes(1);
98
+ const updater = setValue.mock.calls[0][0] as (value: unknown) => unknown;
99
+ expect(updater({})).toEqual({
100
+ selections: [],
101
+ points: [
102
+ {
103
+ x: "Feb",
104
+ y: 18,
105
+ curveNumber: 0,
106
+ pointNumber: 1,
107
+ pointIndex: 1,
108
+ },
109
+ ],
110
+ indices: [1],
111
+ range: undefined,
112
+ });
113
+ });
114
+ });
@@ -0,0 +1,199 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import type * as Plotly from "plotly.js";
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import {
6
+ extractIndices,
7
+ extractPoints,
8
+ hasPureLineTrace,
9
+ lineSelectionButtons,
10
+ type ModeBarButton,
11
+ mergeModeBarButtonsToAdd,
12
+ shouldHandleClickSelection,
13
+ } from "../selection";
14
+
15
+ function createTrace(trace: Partial<Plotly.PlotData>): Plotly.Data {
16
+ return trace as unknown as Plotly.Data;
17
+ }
18
+
19
+ function createPlotDatum<T extends object>(overrides: T): Plotly.PlotDatum & T {
20
+ return overrides as unknown as Plotly.PlotDatum & T;
21
+ }
22
+
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
+ });
32
+
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);
41
+ });
42
+ });
43
+
44
+ describe("lineSelectionButtons", () => {
45
+ it("creates dragmode buttons that update dragmode", () => {
46
+ const setDragmode = vi.fn();
47
+ const buttons = lineSelectionButtons(setDragmode);
48
+
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"]);
55
+
56
+ const graphDiv = document.createElement(
57
+ "div",
58
+ ) as unknown as Plotly.PlotlyHTMLElement;
59
+ const clickEvent = new MouseEvent("click");
60
+
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");
66
+ });
67
+ });
68
+
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
+ });
86
+
87
+ describe("shouldHandleClickSelection", () => {
88
+ it("accepts bar clicks", () => {
89
+ const barPoint = createPlotDatum({
90
+ data: { type: "bar" },
91
+ });
92
+
93
+ expect(shouldHandleClickSelection([barPoint])).toBe(true);
94
+ });
95
+
96
+ it("accepts heatmap clicks", () => {
97
+ const heatmapPoint = createPlotDatum({
98
+ data: { type: "heatmap" },
99
+ });
100
+
101
+ expect(shouldHandleClickSelection([heatmapPoint])).toBe(true);
102
+ });
103
+
104
+ it("accepts histogram clicks", () => {
105
+ const histogramPoint = createPlotDatum({
106
+ data: { type: "histogram" },
107
+ });
108
+
109
+ expect(shouldHandleClickSelection([histogramPoint])).toBe(true);
110
+ });
111
+
112
+ it("accepts scatter clicks when Plotly omits mode", () => {
113
+ const linePoint = createPlotDatum({
114
+ data: { type: "scatter" },
115
+ });
116
+
117
+ expect(shouldHandleClickSelection([linePoint])).toBe(true);
118
+ });
119
+
120
+ it("rejects non-line scatter marker clicks", () => {
121
+ const markerPoint = createPlotDatum({
122
+ data: { type: "scatter", mode: "markers" },
123
+ });
124
+
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
+ },
153
+ });
154
+
155
+ expect(extractPoints([point])).toEqual([
156
+ { pointNumber: 1, pointIndex: 1, x: 20, y: 200 },
157
+ ]);
158
+ });
159
+
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
+ },
182
+ ]);
183
+ });
184
+
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
+ },
195
+ });
196
+
197
+ expect(extractPoints([point])).toEqual([{ x: 1, y: 2, z: 3 }]);
198
+ });
199
+ });
@@ -0,0 +1,333 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { pick } from "lodash-es";
4
+ import type * as Plotly from "plotly.js";
5
+
6
+ import { createParser, type PlotlyTemplateParser } from "./parse-from-template";
7
+
8
+ type AxisName = string;
9
+ type AxisDatum = unknown;
10
+
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;
26
+
27
+ const LINE_CLICK_TRACE_TYPES = new Set(["scatter", "scattergl"]);
28
+
29
+ const STANDARD_POINT_KEYS: string[] = [
30
+ "x",
31
+ "y",
32
+ "z",
33
+ "lat",
34
+ "lon",
35
+ "curveNumber",
36
+ "pointNumber",
37
+ "pointNumbers",
38
+ "pointIndex",
39
+ ] as const;
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
+
93
+ function getPointIndex(point: Plotly.PlotDatum): number | undefined {
94
+ const pointIndex = asFiniteNumber(point.pointIndex);
95
+ if (pointIndex !== undefined) {
96
+ return pointIndex;
97
+ }
98
+
99
+ const pointNumber = asFiniteNumber(point.pointNumber);
100
+ if (pointNumber !== undefined) {
101
+ return pointNumber;
102
+ }
103
+
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");
126
+ }
127
+
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");
134
+ }
135
+
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);
151
+ });
152
+ }
153
+
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
+ }
288
+ }
289
+
290
+ return nextFields;
291
+ }
292
+
293
+ export function extractPoints(
294
+ points: readonly Plotly.PlotDatum[],
295
+ ): Record<AxisName, AxisDatum>[] {
296
+ let parser: PlotlyTemplateParser | undefined;
297
+
298
+ return points.map((point) => {
299
+ const standardPointFields = withInferredXY(
300
+ point,
301
+ pick(point, STANDARD_POINT_KEYS),
302
+ );
303
+
304
+ const trace = getTraceSource(point);
305
+
306
+ // Get the first hovertemplate
307
+ const hovertemplate = Array.isArray(trace.hovertemplate)
308
+ ? trace.hovertemplate[0]
309
+ : trace.hovertemplate;
310
+
311
+ // For chart types with standard point keys (e.g. heatmaps),
312
+ // or when there's no hovertemplate, pick keys directly from the point.
313
+ if (!hovertemplate || trace.type === "heatmap") {
314
+ return standardPointFields;
315
+ }
316
+
317
+ parser = parser
318
+ ? parser.update(hovertemplate)
319
+ : createParser(hovertemplate);
320
+ return {
321
+ ...standardPointFields,
322
+ ...parser.parse(point),
323
+ };
324
+ });
325
+ }
326
+
327
+ export function extractSunburstPoints(
328
+ points: readonly Plotly.PlotDatum[],
329
+ ): Record<string, unknown>[] {
330
+ return points.map((point) => pick(point, SUNBURST_DATA_KEYS));
331
+ }
332
+
333
+ export const extractTreemapPoints = extractSunburstPoints;
@@ -578,20 +578,19 @@ describe("makeSelectable", () => {
578
578
  `);
579
579
  });
580
580
 
581
- it.each([
582
- "errorbar",
583
- "errorband",
584
- "boxplot",
585
- ])("should return the same spec if mark is %s", (mark) => {
586
- const spec = {
587
- mark,
588
- } as unknown as VegaLiteSpec;
589
- const newSpec = makeSelectable(spec, {});
590
- expect(newSpec).toEqual(spec);
591
- expect(getSelectionParamNames(newSpec)).toEqual([]);
592
- expect(newSpec).toMatchSnapshot();
593
- expect(parse(newSpec)).toBeDefined();
594
- });
581
+ it.each(["errorbar", "errorband", "boxplot"])(
582
+ "should return the same spec if mark is %s",
583
+ (mark) => {
584
+ const spec = {
585
+ mark,
586
+ } as unknown as VegaLiteSpec;
587
+ const newSpec = makeSelectable(spec, {});
588
+ expect(newSpec).toEqual(spec);
589
+ expect(getSelectionParamNames(newSpec)).toEqual([]);
590
+ expect(newSpec).toMatchSnapshot();
591
+ expect(parse(newSpec)).toBeDefined();
592
+ },
593
+ );
595
594
 
596
595
  it("should add legend selection to composite charts (issue #6676)", () => {
597
596
  // Test case from https://github.com/marimo-team/marimo/issues/6676