@marimo-team/islands 0.25.3-dev13 → 0.25.3-dev134

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 (437) hide show
  1. package/dist/{ConnectedDataExplorerComponent-CNMliVzw.js → ConnectedDataExplorerComponent-BAiSVaHj.js} +17 -15
  2. package/dist/{ErrorBoundary-BWYjMPq8.js → ErrorBoundary-CImYa86e.js} +16 -16
  3. package/dist/{abnfDiagram-VCTEODGH-D-Ez91gV.js → abnfDiagram-VCTEODGH-BBHN92-F.js} +2 -2
  4. package/dist/any-language-editor-3E24SnCV.js +75 -0
  5. package/dist/{architectureDiagram-5GKGNRK7-CTpcNtO8.js → architectureDiagram-5GKGNRK7-C0Rd64qh.js} +1 -1
  6. package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
  7. package/dist/assets/__vite-browser-external-SaDYcg7x.js +1 -0
  8. package/dist/assets/{worker-HPY9fbhz.js → worker-CfME3uyi.js} +3 -2
  9. package/dist/{button-CvjkwdSk.js → button-BQRdqQi1.js} +199 -192
  10. package/dist/{capabilities-CQ5IjhzL.js → capabilities-CI4xUPsp.js} +2 -2
  11. package/dist/{chat-ui-DaHynk8g.js → chat-ui-DqcauBr6.js} +1142 -1141
  12. package/dist/{check-EDwSwP89.js → check-SpOMoCLP.js} +1 -1
  13. package/dist/{chunk-IMKFNOWR-DGxi-zeo.js → chunk-IMKFNOWR-hJXey5p2.js} +1 -1
  14. package/dist/{chunk-SHT3W25Y-CV9J3rAB.js → chunk-SHT3W25Y-JMzTmmVW.js} +1 -1
  15. package/dist/{chunk-SVP7TREG-BL-9Fqgg.js → chunk-SVP7TREG-BIyoHTXi.js} +1 -1
  16. package/dist/{chunk-TICWLB2K-Cu_XMjDM.js → chunk-TICWLB2K-CCzH2Uwa.js} +1 -1
  17. package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
  18. package/dist/{classDiagram-ZZMXUADV-BXXV13Hq.js → classDiagram-ZZMXUADV-DB1kOPS6.js} +2 -2
  19. package/dist/{classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js → classDiagram-v2-VYDZK3BY-CPaFL0OB.js} +2 -2
  20. package/dist/{code-visibility-O0uJkFyz.js → code-visibility-BZoWT5f1.js} +6 -6
  21. package/dist/{common-PAvTWnXO.js → common-CGmIoGK3.js} +6762 -6586
  22. package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-C1trdvVt.js} +3 -3
  23. package/dist/{cynefinDiagram-5FMLGOSQ-CH96rwba.js → cynefinDiagram-5FMLGOSQ-CF93uliH.js} +1 -1
  24. package/dist/{dagre-GXQ25YYZ-BD3PmpFY.js → dagre-GXQ25YYZ-BBB-_p9D.js} +1 -1
  25. package/dist/{data-grid-overlay-editor-DeWySy0K.js → data-grid-overlay-editor-CtOna148.js} +5 -5
  26. package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-0PJ-9N5n.js} +1 -1
  27. package/dist/{diagram-S7CK7UJ4-BsSJJldB.js → diagram-S7CK7UJ4-BynCnbyt.js} +1 -1
  28. package/dist/{diagram-UQ7AKVKN-muQtNZc4.js → diagram-UQ7AKVKN-svOxikMa.js} +1 -1
  29. package/dist/{diagram-VX7I27RA-CdhuZAqR.js → diagram-VX7I27RA-BVQSnPx0.js} +1 -1
  30. package/dist/{diagram-Z3DM3KII-BD8mXjOj.js → diagram-Z3DM3KII-nR1J0BH5.js} +1 -1
  31. package/dist/{dialog-CEBFZNyc.js → dialog-DgeqyrEZ.js} +3 -3
  32. package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
  33. package/dist/{dropdown-menu-CP_qCYHC.js → dropdown-menu-CgL3fm8P.js} +1262 -1229
  34. package/dist/{ebnfDiagram-PWID7BFC-BIN1sweE.js → ebnfDiagram-PWID7BFC-C9tq9-Qo.js} +2 -2
  35. package/dist/{erDiagram-RLTQ6QDP-Ctp5cfoN.js → erDiagram-RLTQ6QDP-BiaeB7Fi.js} +1 -1
  36. package/dist/{error-banner-D-WCIiMt.js → error-banner-D40pmfQQ.js} +83 -83
  37. package/dist/{error-entries-Bv6bZPF3.js → error-entries-CmFDQ9CA.js} +1018 -977
  38. package/dist/{feedback-button-WbN_sdUE.js → feedback-button-7ZbDdjfz.js} +23 -23
  39. package/dist/{flowDiagram-HODETNUW-3EfLrlFb.js → flowDiagram-HODETNUW-f-sLm87k.js} +2 -2
  40. package/dist/{formats-27CW_UP4.js → formats-CCxaLPNs.js} +3 -3
  41. package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
  42. package/dist/{glide-data-editor-_oc1WE9x.js → glide-data-editor-D72zmCan.js} +370 -368
  43. package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Dz_F4pAY.js} +16 -16
  44. package/dist/{globals-C-fUiztk.js → globals-DE2S2io9.js} +3 -3
  45. package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
  46. package/dist/{html-to-image-Bf8QMGi4.js → html-to-image-CfgsWObF.js} +630 -593
  47. package/dist/{infoDiagram-27XIBGKW-DipQoHxl.js → infoDiagram-27XIBGKW-BHrWywBe.js} +1 -1
  48. package/dist/{ishikawaDiagram-5VMMS53U-DgFMjSGz.js → ishikawaDiagram-5VMMS53U-Da4s0Reg.js} +1 -1
  49. package/dist/{kanban-definition-UXKFOSKX-w1ACVjgl.js → kanban-definition-UXKFOSKX-DDf1GVLj.js} +1 -1
  50. package/dist/{label-BDKF3Afc.js → label-D9_-zSg-.js} +3 -3
  51. package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
  52. package/dist/{loader-BQOfjTIJ.js → loader-DOVB0Syl.js} +12 -12
  53. package/dist/main.js +7441 -7115
  54. package/dist/{mermaid-CHo49Yv-.js → mermaid-ClwTt7OD.js} +38 -38
  55. package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
  56. package/dist/{mindmap-definition-YA3MSWOX-CrT0KSfX.js → mindmap-definition-YA3MSWOX-D60oMZtT.js} +1 -1
  57. package/dist/{number-overlay-editor-DdPkN3h1.js → number-overlay-editor-Br0lPnFE.js} +3 -3
  58. package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
  59. package/dist/{once-BtE-wKRL.js → once-eG8Vmvjd.js} +1 -1
  60. package/dist/{pegDiagram-XKGWAZYB-BqvywNJA.js → pegDiagram-XKGWAZYB-B9wFpMga.js} +2 -2
  61. package/dist/{pieDiagram-E7YTZNPT-BfxJ_XCc.js → pieDiagram-E7YTZNPT-BZwvMuBT.js} +1 -1
  62. package/dist/precisionRound-DhAzeRcm.js +49 -0
  63. package/dist/{process-output-BZg56CCb.js → process-output-B64ZYWuI.js} +5 -5
  64. package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
  65. package/dist/{railroadDiagram-O6MQD6OU-8vFLVTPu.js → railroadDiagram-O6MQD6OU-DqhZGsN2.js} +2 -2
  66. package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
  67. package/dist/{requirementDiagram-BXWQKSXE-a_MxGvxe.js → requirementDiagram-BXWQKSXE-CL5dYARV.js} +1 -1
  68. package/dist/{reveal-component-Bs9X_N3n.js → reveal-component-DnbPU885.js} +14 -14
  69. package/dist/{select-BButFi-d.js → select-CkF_GBxO.js} +537 -458
  70. package/dist/{sequenceDiagram-WJ2MYXX4-CXFMnxGh.js → sequenceDiagram-WJ2MYXX4-BSUCRd2x.js} +1 -1
  71. package/dist/{spec-BwarBpUJ.js → spec-DiSh_qJC.js} +41 -11
  72. package/dist/{stateDiagram-D77RDMKH-DQeA_CwI.js → stateDiagram-D77RDMKH-B-1Hvyb0.js} +2 -2
  73. package/dist/{stateDiagram-v2-MP3YSRHH-BxxQucm1.js → stateDiagram-v2-MP3YSRHH--HtiR4yB.js} +2 -2
  74. package/dist/style.css +1 -1
  75. package/dist/{swimlanes-42K2YHIH-Do6L-cYg.js → swimlanes-42K2YHIH-Bdfhrd-2.js} +1 -1
  76. package/dist/{swimlanesDiagram-VR7AAH4N-DUzPvSVU.js → swimlanesDiagram-VR7AAH4N-BJIfxSz0.js} +2 -2
  77. package/dist/{swiper-component-DKntyn8p.js → swiper-component-BkCm-kLg.js} +2 -2
  78. package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
  79. package/dist/{timeline-definition-24CTP7MA-D3c46WM5.js → timeline-definition-24CTP7MA-Cs4EfFD3.js} +1 -1
  80. package/dist/{toDate-UtiwmnH2.js → toDate-DGvRHwTk.js} +19 -18
  81. package/dist/{toast-B9UMEi8u.js → toast-Btbve3Lm.js} +231 -213
  82. package/dist/{tooltip-BO1REbqs.js → tooltip-CJ4Rqasp.js} +327 -303
  83. package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-CmGwRNId.js} +1482 -1433
  84. package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-BmVgnMP5.js} +15 -14
  85. package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-eCL_SdeG.js} +2 -2
  86. package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-Ch_AeGu6.js} +1 -1
  87. package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-CSdWn6NP.js} +1 -1
  88. package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-B5BX921H.js} +3 -3
  89. package/dist/{useTheme-CRnjkku0.js → useTheme-Bq9pi_vz.js} +6 -6
  90. package/dist/{vega-component-BeF9dfyO.js → vega-component-BgyZ-COt.js} +147 -147
  91. package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
  92. package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
  93. package/dist/{vennDiagram-4TSXK5OY-BSZD8JcZ.js → vennDiagram-4TSXK5OY-DRu0vRjO.js} +1 -1
  94. package/dist/{wardleyDiagram-VM6X3IG4-48cUWkfA.js → wardleyDiagram-VM6X3IG4-DzbDxwDY.js} +1 -1
  95. package/dist/{xychartDiagram-S5SC5T6Z-CswYgYeu.js → xychartDiagram-S5SC5T6Z-CLWj6ZFQ.js} +2 -2
  96. package/package.json +14 -14
  97. package/src/__tests__/host-imports.test.ts +1 -0
  98. package/src/__tests__/module-entry.test.ts +32 -0
  99. package/src/__tests__/mount.test.ts +0 -20
  100. package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
  101. package/src/components/ai/ai-model-dropdown.tsx +11 -6
  102. package/src/components/app-config/ai-config.tsx +7 -2
  103. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  104. package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
  105. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  106. package/src/components/chat/acp/blocks.tsx +3 -2
  107. package/src/components/chat/acp/session-tabs.tsx +8 -3
  108. package/src/components/chat/chat-components.tsx +2 -0
  109. package/src/components/chat/chat-panel.tsx +9 -8
  110. package/src/components/chat/chat-utils.ts +4 -8
  111. package/src/components/data-table/TableTopBar.tsx +2 -0
  112. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  113. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  114. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  115. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  116. package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
  117. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  118. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  119. package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
  120. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  121. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  122. package/src/components/data-table/cell-selection/feature.ts +2 -2
  123. package/src/components/data-table/cell-selection/types.ts +2 -2
  124. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  125. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  126. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  127. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  128. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  129. package/src/components/data-table/charts/charts.tsx +40 -30
  130. package/src/components/data-table/charts/storage.ts +16 -5
  131. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  132. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  133. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  134. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  135. package/src/components/data-table/columns.tsx +45 -46
  136. package/src/components/data-table/context-menu.tsx +36 -20
  137. package/src/components/data-table/data-table.tsx +13 -1
  138. package/src/components/data-table/date-popover.tsx +6 -4
  139. package/src/components/data-table/export-actions.tsx +76 -48
  140. package/src/components/data-table/range-focus/atoms.ts +4 -6
  141. package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
  142. package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
  143. package/src/components/data-table/renderers.tsx +16 -7
  144. package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
  145. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
  146. package/src/components/data-table/search-highlight.tsx +79 -0
  147. package/src/components/data-table/types.ts +1 -0
  148. package/src/components/data-table/url-detector.tsx +49 -9
  149. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  150. package/src/components/datasources/__tests__/column-preview.test.tsx +63 -2
  151. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  152. package/src/components/datasources/column-preview.tsx +8 -2
  153. package/src/components/datasources/datasources.tsx +48 -35
  154. package/src/components/debugger/debugger-code.tsx +5 -0
  155. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  156. package/src/components/dependency-graph/elements.ts +24 -21
  157. package/src/components/dependency-graph/panels.tsx +2 -1
  158. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  159. package/src/components/editor/SortableCell.tsx +1 -3
  160. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  161. package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
  162. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
  163. package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
  164. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  165. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  166. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  167. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  168. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  169. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  170. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  171. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  172. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  173. package/src/components/editor/cell/collapse.tsx +5 -3
  174. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  175. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  176. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  177. package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
  178. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
  179. package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
  180. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  181. package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
  182. package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -1
  183. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
  184. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  185. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  186. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  187. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  188. package/src/components/editor/connections/components.tsx +1 -1
  189. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  190. package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
  191. package/src/components/editor/controls/command-palette.tsx +22 -10
  192. package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
  193. package/src/components/editor/dynamic-favicon.tsx +21 -40
  194. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +12 -23
  195. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  196. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
  197. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  198. package/src/components/editor/file-tree/file-explorer.tsx +45 -8
  199. package/src/components/editor/file-tree/file-name-input.tsx +1 -0
  200. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  201. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  202. package/src/components/editor/header/status.tsx +28 -5
  203. package/src/components/editor/links/__tests__/cell-link.test.tsx +146 -0
  204. package/src/components/editor/links/cell-link.tsx +14 -5
  205. package/src/components/editor/navigation/focus-utils.ts +4 -4
  206. package/src/components/editor/notebook-banner.tsx +1 -0
  207. package/src/components/editor/notebook-cell.tsx +11 -2
  208. package/src/components/editor/output/MarimoTracebackOutput.tsx +19 -17
  209. package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
  210. package/src/components/editor/output/ansi-reduce.ts +33 -20
  211. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  212. package/src/components/editor/package-alert.tsx +14 -7
  213. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
  214. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  215. package/src/components/home/components.tsx +17 -31
  216. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  217. package/src/components/icons/large-spinner.tsx +3 -14
  218. package/src/components/icons/spinner.tsx +1 -1
  219. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  220. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  221. package/src/components/pages/home-page.tsx +164 -91
  222. package/src/components/scratchpad/scratchpad.test.tsx +95 -0
  223. package/src/components/scratchpad/scratchpad.tsx +16 -3
  224. package/src/components/slides/reveal-component.tsx +4 -2
  225. package/src/components/slides/slide-cell-view.tsx +1 -1
  226. package/src/components/slides/slide-form.tsx +1 -0
  227. package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
  228. package/src/components/tracing/tracing.tsx +45 -41
  229. package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
  230. package/src/components/ui/alert-dialog.tsx +1 -1
  231. package/src/components/ui/draggable-popover.tsx +24 -2
  232. package/src/core/ai/context/providers/datasource.ts +6 -2
  233. package/src/core/ai/tools/base.ts +16 -7
  234. package/src/core/ai/tools/edit-notebook-tool.ts +47 -45
  235. package/src/core/ai/tools/registry.ts +13 -8
  236. package/src/core/ai/tools/sample-tool.ts +6 -7
  237. package/src/core/attachments/state.ts +5 -8
  238. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  239. package/src/core/cells/__tests__/cell.test.ts +39 -0
  240. package/src/core/cells/__tests__/cells.test.ts +3 -1
  241. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  242. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  243. package/src/core/cells/__tests__/session.test.ts +117 -36
  244. package/src/core/cells/cell.ts +14 -3
  245. package/src/core/cells/cells.ts +3 -1
  246. package/src/core/cells/pending-delete-service.ts +2 -6
  247. package/src/core/cells/session.ts +1 -1
  248. package/src/core/cells/types.ts +10 -1
  249. package/src/core/codemirror/__tests__/setup.test.ts +29 -29
  250. package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -0
  251. package/src/core/codemirror/cells/extensions.ts +7 -3
  252. package/src/core/codemirror/keymaps/vim.ts +2 -6
  253. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  254. package/src/core/codemirror/language/extension.ts +8 -8
  255. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  256. package/src/core/codemirror/language/languages/python.ts +16 -10
  257. package/src/core/codemirror/language/languages/sql/sql.ts +9 -7
  258. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  259. package/src/core/codemirror/language/types.ts +7 -7
  260. package/src/core/codemirror/rtc/extension.ts +24 -12
  261. package/src/core/codemirror/rtc/loro/awareness.ts +34 -19
  262. package/src/core/codemirror/rtc/loro/sync.ts +1 -0
  263. package/src/core/codemirror/utils.ts +1 -0
  264. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  265. package/src/core/datasets/data-source-discovery.ts +4 -6
  266. package/src/core/document/__tests__/codemirror.test.ts +485 -0
  267. package/src/core/document/__tests__/draft.test.ts +322 -0
  268. package/src/core/document/__tests__/fixtures.ts +92 -0
  269. package/src/core/document/__tests__/hooks.test.tsx +386 -0
  270. package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  271. package/src/core/document/__tests__/model.test.ts +191 -0
  272. package/src/core/document/__tests__/protocol-fixtures.ts +38 -0
  273. package/src/core/document/__tests__/recovery-scenarios.test.ts +230 -0
  274. package/src/core/document/__tests__/scenario-fixture.ts +43 -0
  275. package/src/core/document/__tests__/scenarios.test.ts +205 -0
  276. package/src/core/document/__tests__/state.test.ts +438 -0
  277. package/src/core/document/__tests__/submission.test.ts +203 -0
  278. package/src/core/document/__tests__/test-host.test.ts +92 -0
  279. package/src/core/document/__tests__/test-host.ts +247 -0
  280. package/src/core/document/__tests__/test-notebook.ts +149 -0
  281. package/src/core/document/__tests__/user.ts +61 -0
  282. package/src/core/document/codemirror.ts +311 -0
  283. package/src/core/document/draft.ts +472 -0
  284. package/src/core/document/hooks.tsx +118 -0
  285. package/src/core/document/index.ts +15 -0
  286. package/src/core/document/model.ts +55 -0
  287. package/src/core/document/select.ts +41 -0
  288. package/src/core/document/state.ts +374 -0
  289. package/src/core/document/sync.ts +369 -0
  290. package/src/core/host/AGENTS.md +8 -2
  291. package/src/core/host/__tests__/attachments.test.ts +56 -0
  292. package/src/core/host/__tests__/document.test.ts +126 -0
  293. package/src/core/host/__tests__/fixtures.ts +71 -0
  294. package/src/core/host/__tests__/replica.test.ts +550 -0
  295. package/src/core/host/__tests__/static.test.ts +169 -0
  296. package/src/core/host/__tests__/wasm.test.ts +162 -0
  297. package/src/core/host/backend.ts +19 -0
  298. package/src/core/host/connection.ts +1 -40
  299. package/src/core/host/index.ts +14 -0
  300. package/src/core/host/replica.ts +286 -0
  301. package/src/core/host/resources/attachments.ts +22 -0
  302. package/src/core/host/resources/document.ts +101 -0
  303. package/src/core/host/static.ts +72 -0
  304. package/src/core/host/wasm.ts +102 -0
  305. package/src/core/hotkeys/actions.ts +2 -4
  306. package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
  307. package/src/core/islands/bootstrap.ts +0 -1
  308. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  309. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  310. package/src/core/network/connection.ts +4 -2
  311. package/src/core/network/useConnectionNotice.ts +8 -2
  312. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  313. package/src/core/runtime/__tests__/runtime.test.ts +32 -58
  314. package/src/core/runtime/runtime.ts +38 -42
  315. package/src/core/runtime/types.ts +0 -2
  316. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  317. package/src/core/state/jotai.ts +1 -1
  318. package/src/core/static/__tests__/files.test.ts +38 -10
  319. package/src/core/static/files.ts +2 -0
  320. package/src/core/wasm/bridge.ts +38 -10
  321. package/src/core/wasm/store.ts +3 -3
  322. package/src/core/wasm/worker/bootstrap.ts +2 -1
  323. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  324. package/src/core/wasm/worker/types.ts +11 -0
  325. package/src/core/wasm/worker/worker.ts +48 -0
  326. package/src/core/websocket/__tests__/backend.test.ts +200 -0
  327. package/src/core/websocket/__tests__/connection-state.test.ts +0 -12
  328. package/src/core/websocket/__tests__/useNotebookReplica.hook.test.tsx +141 -0
  329. package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +154 -428
  330. package/src/core/websocket/backend.ts +106 -0
  331. package/src/core/websocket/connection-state.ts +1 -11
  332. package/src/core/websocket/state.ts +7 -0
  333. package/src/core/websocket/transports/__tests__/notebook.test.ts +445 -0
  334. package/src/core/websocket/transports/notebook.ts +190 -0
  335. package/src/core/websocket/types.ts +2 -2
  336. package/src/core/websocket/useNotebookReplica.ts +69 -0
  337. package/src/core/websocket/useNotebookSession.tsx +10 -22
  338. package/src/css/globals.css +5 -0
  339. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  340. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  341. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  342. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  343. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  344. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  345. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  346. package/src/hooks/useAsyncData.ts +3 -3
  347. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  348. package/src/hooks/useDebounce.ts +6 -10
  349. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  350. package/src/hooks/useDelayElapsed.ts +14 -10
  351. package/src/hooks/useElapsedTime.ts +3 -3
  352. package/src/hooks/useEventListener.ts +1 -2
  353. package/src/hooks/useInputHistory.ts +4 -7
  354. package/src/hooks/useInternalStateWithSync.ts +6 -6
  355. package/src/hooks/useResizeObserver.ts +4 -7
  356. package/src/hooks/useScript.ts +67 -70
  357. package/src/hooks/useStateForKey.ts +93 -0
  358. package/src/mount.tsx +0 -4
  359. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  360. package/src/plugins/core/registerReactComponent.tsx +1 -1
  361. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  362. package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
  363. package/src/plugins/impl/DataTablePlugin.tsx +29 -33
  364. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  365. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  366. package/src/plugins/impl/FormPlugin.tsx +1 -1
  367. package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
  368. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  369. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  370. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +117 -1
  371. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  372. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  373. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  374. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  375. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  376. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  377. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  378. package/src/plugins/impl/chat/chat-ui.tsx +14 -11
  379. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  380. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  381. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  382. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  383. package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
  384. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  385. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  386. package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
  387. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
  388. package/src/plugins/impl/data-frames/panel.tsx +43 -16
  389. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  390. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  391. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  392. package/src/plugins/impl/panel/utils.ts +17 -0
  393. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  394. package/src/plugins/impl/plotly/selection.ts +25 -19
  395. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  396. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  397. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  398. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  399. package/src/stories/button.stories.tsx +1 -0
  400. package/src/stories/editor.stories.tsx +24 -17
  401. package/src/stories/form-wrapper.stories.tsx +2 -2
  402. package/src/stories/select.stories.tsx +2 -2
  403. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  404. package/src/utils/__tests__/arrays.test.ts +35 -0
  405. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  406. package/src/utils/__tests__/numbers.test.ts +48 -1
  407. package/src/utils/__tests__/objects.test.ts +21 -0
  408. package/src/utils/arrays.ts +10 -0
  409. package/src/utils/blob.ts +1 -1
  410. package/src/utils/numbers.ts +28 -17
  411. package/src/utils/objects.ts +3 -7
  412. package/src/utils/once.ts +19 -19
  413. package/src/utils/tracer.ts +9 -5
  414. package/src/utils/zod-utils.ts +1 -3
  415. package/dist/any-language-editor-DZ_YTVus.js +0 -76
  416. package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
  417. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
  418. package/dist/precisionRound-DMIVQrt2.js +0 -97
  419. package/src/core/host/__tests__/events.test.ts +0 -46
  420. package/src/core/host/__tests__/notebook-client.test.ts +0 -191
  421. package/src/core/host/__tests__/protocol.test.ts +0 -214
  422. package/src/core/host/events.ts +0 -60
  423. package/src/core/host/notebook-client.ts +0 -226
  424. package/src/core/host/protocol.ts +0 -1943
  425. package/src/core/state/__tests__/jotai.test.ts +0 -25
  426. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  427. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +0 -140
  428. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  429. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -335
  430. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  431. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  432. package/src/core/websocket/transports/basic.ts +0 -102
  433. package/src/core/websocket/transports/create.ts +0 -31
  434. package/src/core/websocket/transports/sse.ts +0 -308
  435. package/src/core/websocket/transports/transport.ts +0 -50
  436. package/src/core/websocket/transports/ws.ts +0 -98
  437. package/src/core/websocket/useNotebookClient.ts +0 -80
@@ -45,45 +45,71 @@ export const useCellRangeSelection = <TData>({
45
45
  },
46
46
  );
47
47
 
48
- const handleCellsKeyDown = useEvent((e: React.KeyboardEvent<HTMLElement>) => {
49
- switch (e.key) {
50
- case "c":
51
- if (Events.isMetaOrCtrl(e)) {
52
- handleCopy();
53
- }
54
- break;
55
- case "ArrowDown":
56
- e.preventDefault();
57
- navigate(e, "down");
58
- break;
59
- case "ArrowUp":
60
- e.preventDefault();
61
- navigate(e, "up");
62
- break;
63
- case "ArrowLeft":
64
- e.preventDefault();
65
- navigate(e, "left");
66
- break;
67
- case "ArrowRight":
68
- e.preventDefault();
69
- navigate(e, "right");
70
- break;
71
- case "Enter":
72
- e.preventDefault();
73
- actions.toggleCurrentRowSelection(table);
74
- break;
75
- case "Escape":
76
- actions.clearSelection();
77
- break;
78
- case "a":
79
- if (e.metaKey || e.ctrlKey) {
80
- e.preventDefault();
81
- actions.selectAllCells(table);
82
- }
83
- break;
84
- }
48
+ const handleCellFocus = useEvent((cell: Cell<TData, unknown>) => {
49
+ actions.setFocusedCell({
50
+ cellId: cell.id,
51
+ rowId: cell.row.id,
52
+ columnId: cell.column.id,
53
+ });
85
54
  });
86
55
 
56
+ const handleCellsKeyDown = useEvent(
57
+ (e: React.KeyboardEvent<HTMLElement>, cell: Cell<TData, unknown>) => {
58
+ if (e.defaultPrevented || isInteractiveTarget(e)) {
59
+ return;
60
+ }
61
+ switch (e.key) {
62
+ case "c":
63
+ if (Events.isMetaOrCtrl(e)) {
64
+ handleCopy();
65
+ }
66
+ break;
67
+ case "ArrowDown":
68
+ e.preventDefault();
69
+ navigate(e, "down");
70
+ break;
71
+ case "ArrowUp":
72
+ e.preventDefault();
73
+ navigate(e, "up");
74
+ break;
75
+ case "ArrowLeft":
76
+ e.preventDefault();
77
+ navigate(e, "left");
78
+ break;
79
+ case "ArrowRight":
80
+ e.preventDefault();
81
+ navigate(e, "right");
82
+ break;
83
+ case "Enter":
84
+ case " ":
85
+ e.preventDefault();
86
+ updateSelection(
87
+ {
88
+ cellId: cell.id,
89
+ rowId: cell.row.id,
90
+ columnId: cell.column.id,
91
+ },
92
+ false,
93
+ );
94
+ if (table.options.enableCellSelection) {
95
+ cell.toggleSelected?.();
96
+ } else if (cell.row.getCanSelect()) {
97
+ cell.row.toggleSelected();
98
+ }
99
+ break;
100
+ case "Escape":
101
+ actions.clearSelection();
102
+ break;
103
+ case "a":
104
+ if (e.metaKey || e.ctrlKey) {
105
+ e.preventDefault();
106
+ actions.selectAllCells(table);
107
+ }
108
+ break;
109
+ }
110
+ },
111
+ );
112
+
87
113
  const handleCellMouseDown = useEvent(
88
114
  (e: React.MouseEvent, cell: Cell<TData, unknown>) => {
89
115
  // Right-clicks will trigger context menu, so avoid updating selected cells
@@ -122,6 +148,7 @@ export const useCellRangeSelection = <TData>({
122
148
  handleCellMouseOver,
123
149
  handleCopy,
124
150
  handleCellsKeyDown,
151
+ handleCellFocus,
125
152
  updateSelection,
126
153
  clearSelection: actions.clearSelection,
127
154
  };
@@ -148,7 +175,7 @@ const CONTENT_WRAPPER_MARIMO_TAGS: ReadonlySet<string> = new Set([
148
175
  * shadow host (e.g. `<marimo-lazy>`), hiding any genuinely interactive
149
176
  * descendants rendered inside the shadow tree.
150
177
  */
151
- export function isInteractiveTarget(e: React.MouseEvent): boolean {
178
+ export function isInteractiveTarget(e: React.SyntheticEvent): boolean {
152
179
  const path: readonly EventTarget[] =
153
180
  typeof e.nativeEvent?.composedPath === "function"
154
181
  ? e.nativeEvent.composedPath()
@@ -1,6 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { atom } from "jotai";
3
+ import { atom, useStore } from "jotai";
4
4
  import { useJotaiEffect } from "@/core/state/jotai";
5
5
  import { Logger } from "@/utils/Logger";
6
6
  import { cellSelectionStateAtom } from "./atoms";
@@ -15,14 +15,21 @@ const focusedCellAtom = atom((get) => get(cellSelectionStateAtom).focusedCell);
15
15
  export function useScrollIntoViewOnFocus(
16
16
  root: React.RefObject<HTMLElement | null>,
17
17
  ) {
18
+ const store = useStore();
18
19
  useJotaiEffect(focusedCellAtom, (focusedCell) => {
19
20
  if (!focusedCell?.cellId) {
20
21
  return;
21
22
  }
22
23
 
23
24
  const scroll = () => {
25
+ if (
26
+ !root.current ||
27
+ store.get(focusedCellAtom)?.cellId !== focusedCell.cellId
28
+ ) {
29
+ return;
30
+ }
24
31
  // Check if this cell contains the CellRangeSelectionIndicator with our cellId
25
- const indicator = root.current?.querySelector(
32
+ const indicator = root.current?.querySelector<HTMLElement>(
26
33
  `[data-cell-id="${focusedCell.cellId}"]`,
27
34
  );
28
35
  if (!indicator) {
@@ -33,6 +40,18 @@ export function useScrollIntoViewOnFocus(
33
40
  return;
34
41
  }
35
42
 
43
+ const focusRoot = root.current.getRootNode();
44
+ const activeElement =
45
+ focusRoot instanceof ShadowRoot || focusRoot instanceof Document
46
+ ? focusRoot.activeElement
47
+ : null;
48
+ if (
49
+ root.current.contains(activeElement) &&
50
+ activeElement?.matches("td[data-cell-id]")
51
+ ) {
52
+ indicator.focus({ preventScroll: true });
53
+ }
54
+
36
55
  indicator.scrollIntoView({
37
56
  behavior: "smooth",
38
57
  block: "nearest",
@@ -124,6 +124,7 @@ export const DataTableBody = <TData,>({
124
124
  handleCellMouseUp,
125
125
  handleCellMouseOver,
126
126
  handleCellsKeyDown,
127
+ handleCellFocus,
127
128
  handleCopy: handleCopyAllCells,
128
129
  } = useCellRangeSelection({ table });
129
130
 
@@ -147,10 +148,16 @@ export const DataTableBody = <TData,>({
147
148
  return (
148
149
  <TableCell
149
150
  tabIndex={0}
151
+ role={table.options.enableCellSelection ? "gridcell" : undefined}
152
+ aria-selected={
153
+ table.options.enableCellSelection
154
+ ? cell.getIsSelected?.()
155
+ : undefined
156
+ }
150
157
  {...getCellDomProps(cell.id)}
151
158
  key={cell.id}
152
159
  className={cn(
153
- "whitespace-pre truncate outline-hidden border-r border-r-border/75",
160
+ "whitespace-pre truncate outline-hidden focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring border-r border-r-border/75",
154
161
  !fixedWidth && "max-w-[300px]",
155
162
  cell.column.getColumnWrapping &&
156
163
  cell.column.getColumnWrapping?.() === "wrap" &&
@@ -169,7 +176,13 @@ export const DataTableBody = <TData,>({
169
176
  hoverTooltip.handleCellMouseOver(e, cell);
170
177
  }}
171
178
  onMouseLeave={hoverTooltip.handleCellMouseLeave}
172
- onFocus={(e) => hoverTooltip.handleCellFocus(e, cell)}
179
+ onFocus={(e) => {
180
+ if (e.target === e.currentTarget) {
181
+ handleCellFocus(cell);
182
+ }
183
+ hoverTooltip.handleCellFocus(e, cell);
184
+ }}
185
+ onKeyDown={(e) => handleCellsKeyDown(e, cell)}
173
186
  onBlur={hoverTooltip.handleCellBlur}
174
187
  onContextMenu={() => handleContextMenu(cell)}
175
188
  >
@@ -264,11 +277,7 @@ export const DataTableBody = <TData,>({
264
277
  return rows.map((row) => renderRow(row));
265
278
  };
266
279
 
267
- const tableBody = (
268
- <TableBody onKeyDown={handleCellsKeyDown} ref={tableRef}>
269
- {renderRows()}
270
- </TableBody>
271
- );
280
+ const tableBody = <TableBody ref={tableRef}>{renderRows()}</TableBody>;
272
281
 
273
282
  return (
274
283
  <>
@@ -1,6 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { render, screen } from "@testing-library/react";
3
+ import { fireEvent, render, screen } from "@testing-library/react";
4
4
  import { Provider } from "jotai";
5
5
  import { describe, expect, it, vi } from "vitest";
6
6
  import { TooltipProvider } from "@/components/ui/tooltip";
@@ -17,6 +17,29 @@ const renderWithProviders = (component: React.ReactNode) => {
17
17
  };
18
18
 
19
19
  describe("RowExpandedPanel", () => {
20
+ it("preserves Space activation on navigation buttons", async () => {
21
+ const select = vi.fn();
22
+ const navigate = vi.fn();
23
+ renderWithProviders(
24
+ <RowViewerPanel
25
+ rowIdx={0}
26
+ setRowIdx={navigate}
27
+ totalRows={2}
28
+ fieldTypes={[["name", ["string", "string"]]]}
29
+ getRow={async () => ({ rows: [{ name: "Alice" }] })}
30
+ isSelectable={true}
31
+ isRowSelected={false}
32
+ handleRowSelectionChange={select}
33
+ />,
34
+ );
35
+ await screen.findByText("Alice");
36
+ const next = screen.getByRole("button", { name: "Next row" });
37
+ expect(fireEvent.keyDown(next, { key: " " })).toBe(true);
38
+ expect(select).not.toHaveBeenCalled();
39
+ fireEvent.click(next);
40
+ expect(navigate).toHaveBeenCalledExactlyOnceWith(1);
41
+ });
42
+
20
43
  const mockFieldTypes: FieldTypesWithExternalType = [
21
44
  ["name", ["string", "string"]],
22
45
  ["age", ["number", "number"]],
@@ -34,8 +34,10 @@ import { DelayMount } from "@/components/utils/delay-mount";
34
34
  import { useAsyncData } from "@/hooks/useAsyncData";
35
35
  import { Banner, ErrorBanner } from "@/plugins/impl/common/error-banner";
36
36
  import type { GetRowResult } from "@/plugins/impl/DataTablePlugin";
37
+ import { Objects } from "@/utils/objects";
37
38
  import { NAMELESS_COLUMN_PREFIX, renderCellValue } from "../columns";
38
39
  import { prettifyRowCount } from "../pagination";
40
+ import { isInteractiveTarget } from "../range-focus/use-cell-range-selection";
39
41
  import {
40
42
  type FieldTypesWithExternalType,
41
43
  INDEX_COLUMN_NAME,
@@ -88,8 +90,7 @@ export const RowViewerPanel: React.FC<RowViewerPanelProps> = ({
88
90
  handleRowSelectionChange?.((prev) => {
89
91
  if (isRowSelected) {
90
92
  // Remove this row from selection
91
- const { [rowIdx]: removedRow, ...rest } = prev;
92
- return rest;
93
+ return Objects.filter(prev, (_, key) => key !== String(rowIdx));
93
94
  }
94
95
  // Add this row to selection
95
96
  return { ...prev, [rowIdx]: true };
@@ -102,8 +103,7 @@ export const RowViewerPanel: React.FC<RowViewerPanelProps> = ({
102
103
  }
103
104
 
104
105
  const handleKeyDown = (e: KeyboardEvent) => {
105
- // Don't intercept keys when typing in an input
106
- if (e.target instanceof HTMLInputElement) {
106
+ if (e.defaultPrevented || isInteractiveTarget(e)) {
107
107
  return;
108
108
  }
109
109
  switch (e.key) {
@@ -0,0 +1,79 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import type { ReactNode } from "react";
4
+
5
+ /** Half-open `[start, end)` offsets into a string. */
6
+ export type MatchRange = readonly [start: number, end: number];
7
+
8
+ /**
9
+ * Build the pattern that finds `query` in a display string. Returns
10
+ * `undefined` for an empty query. The query is matched as a literal,
11
+ * case-insensitive substring, which mirrors how the backend selects rows.
12
+ * The `u` flag selects Unicode simple case folding, the same folding the
13
+ * backend regex engines use.
14
+ */
15
+ export function buildHighlightPattern(query?: string): RegExp | undefined {
16
+ if (!query) {
17
+ return undefined;
18
+ }
19
+ const escaped = query.replace(/[$()*+.?[\\\]^{|}]/g, "\\$&");
20
+ return new RegExp(escaped, "giu");
21
+ }
22
+
23
+ /** Offsets of every match of `pattern` in `text`. */
24
+ export function findMatchRanges(text: string, pattern?: RegExp): MatchRange[] {
25
+ if (!pattern) {
26
+ return [];
27
+ }
28
+ const ranges: MatchRange[] = [];
29
+ for (const match of text.matchAll(pattern)) {
30
+ ranges.push([match.index, match.index + match[0].length]);
31
+ }
32
+ return ranges;
33
+ }
34
+
35
+ /**
36
+ * Wrap the parts of `text` covered by `ranges` in `<mark>` elements.
37
+ * `ranges` are offsets into a larger string in which `text` starts at
38
+ * `offset`, so one set of ranges can be shared across adjacent segments.
39
+ * Returns `text` unchanged when no range overlaps it.
40
+ */
41
+ export function highlightRanges(
42
+ text: string,
43
+ ranges: readonly MatchRange[],
44
+ offset = 0,
45
+ ): ReactNode {
46
+ const segments: ReactNode[] = [];
47
+ let cursor = 0;
48
+ for (const [rangeStart, rangeEnd] of ranges) {
49
+ const start = Math.max(rangeStart - offset, cursor);
50
+ const end = Math.min(rangeEnd - offset, text.length);
51
+ if (end <= start) {
52
+ continue;
53
+ }
54
+ if (start > cursor) {
55
+ segments.push(text.slice(cursor, start));
56
+ }
57
+ segments.push(
58
+ <mark key={start} className="bg-search-highlight text-inherit">
59
+ {text.slice(start, end)}
60
+ </mark>,
61
+ );
62
+ cursor = end;
63
+ }
64
+ if (segments.length === 0) {
65
+ return text;
66
+ }
67
+ if (cursor < text.length) {
68
+ segments.push(text.slice(cursor));
69
+ }
70
+ return segments;
71
+ }
72
+
73
+ /**
74
+ * Wrap every match of `pattern` in `text` in a `<mark>` element. Returns
75
+ * `text` unchanged when `pattern` is absent or does not match.
76
+ */
77
+ export function highlightMatches(text: string, pattern?: RegExp): ReactNode {
78
+ return highlightRanges(text, findMatchRanges(text, pattern));
79
+ }
@@ -6,6 +6,7 @@ import type { DataType } from "@/core/kernel/messages";
6
6
  declare module "@tanstack/react-table" {
7
7
  interface TableMeta<TData extends RowData> {
8
8
  rawData?: TData[]; // raw data for filtering/copying (present only if format_mapping is provided)
9
+ highlightPattern?: RegExp; // built from the committed search query that produced the current rows
9
10
  }
10
11
  }
11
12
 
@@ -1,7 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { marked } from "marked";
4
- import { useState } from "react";
4
+ import { Fragment, type ReactNode, useState } from "react";
5
5
  import { MarkdownRenderer } from "@/components/markdown/markdown-renderer";
6
6
  import {
7
7
  Popover,
@@ -10,13 +10,20 @@ import {
10
10
  } from "@/components/ui/popover";
11
11
  import { Events } from "@/utils/events";
12
12
  import type { ContentPart } from "@/utils/url-parser";
13
+ import { findMatchRanges, highlightRanges } from "./search-highlight";
13
14
 
14
- const ImageWithFallback = ({ url }: { url: string }) => {
15
+ const ImageWithFallback = ({
16
+ url,
17
+ fallbackLabel,
18
+ }: {
19
+ url: string;
20
+ fallbackLabel?: ReactNode;
21
+ }) => {
15
22
  const [error, setError] = useState(false);
16
23
  const [open, setOpen] = useState(false);
17
24
 
18
25
  if (error) {
19
- return <URLAnchor url={url} />;
26
+ return <URLAnchor url={url}>{fallbackLabel}</URLAnchor>;
20
27
  }
21
28
 
22
29
  return (
@@ -93,21 +100,54 @@ export const MarkdownUrlDetector = ({
93
100
  return <UrlDetector parts={parts} />;
94
101
  };
95
102
 
96
- export const UrlDetector = ({ parts }: { parts: ContentPart[] }) => {
103
+ export const UrlDetector = ({
104
+ parts,
105
+ highlightPattern,
106
+ }: {
107
+ parts: ContentPart[];
108
+ highlightPattern?: RegExp;
109
+ }) => {
110
+ // Match against the whole string so a query that spans a text part and
111
+ // a link is still marked. Each part then takes its slice of the ranges.
112
+ const ranges = highlightPattern
113
+ ? findMatchRanges(parts.map(partText).join(""), highlightPattern)
114
+ : [];
115
+
116
+ let offset = 0;
97
117
  const markup = parts.map((part, idx) => {
118
+ const text = partText(part);
119
+ const label = highlightRanges(text, ranges, offset);
120
+ offset += text.length;
121
+
98
122
  if (part.type === "url") {
99
- return <URLAnchor key={idx} url={part.url} />;
123
+ return (
124
+ <URLAnchor key={idx} url={part.url}>
125
+ {label}
126
+ </URLAnchor>
127
+ );
100
128
  }
101
129
  if (part.type === "image") {
102
- return <ImageWithFallback key={idx} url={part.url} />;
130
+ return (
131
+ <ImageWithFallback key={idx} url={part.url} fallbackLabel={label} />
132
+ );
103
133
  }
104
- return part.value;
134
+ return <Fragment key={idx}>{label}</Fragment>;
105
135
  });
106
136
 
107
137
  return markup;
108
138
  };
109
139
 
110
- const URLAnchor = ({ url }: { url: string }) => {
140
+ function partText(part: ContentPart): string {
141
+ return part.type === "text" ? part.value : part.url;
142
+ }
143
+
144
+ const URLAnchor = ({
145
+ url,
146
+ children,
147
+ }: {
148
+ url: string;
149
+ children?: ReactNode;
150
+ }) => {
111
151
  return (
112
152
  <a
113
153
  href={url}
@@ -116,7 +156,7 @@ const URLAnchor = ({ url }: { url: string }) => {
116
156
  onClick={Events.stopPropagation()}
117
157
  className="text-link hover:underline"
118
158
  >
119
- {url}
159
+ {children ?? url}
120
160
  </a>
121
161
  );
122
162
  };
@@ -0,0 +1,97 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import {
4
+ fireEvent,
5
+ render,
6
+ screen,
7
+ waitFor,
8
+ within,
9
+ } from "@testing-library/react";
10
+ import { createStore, Provider } from "jotai";
11
+ import { beforeAll, describe, expect, it, vi } from "vitest";
12
+ import { MockRequestClient } from "@/__mocks__/requests";
13
+ import { Command, CommandList } from "@/components/ui/command";
14
+ import { TooltipProvider } from "@/components/ui/tooltip";
15
+ import type { DataTable } from "@/core/kernel/messages";
16
+ import { requestClientAtom } from "@/core/network/requests";
17
+ import { copyToClipboard } from "@/utils/copy";
18
+ import { TableList } from "../datasources";
19
+
20
+ vi.mock("@/utils/copy", () => ({
21
+ copyToClipboard: vi.fn().mockResolvedValue(undefined),
22
+ }));
23
+
24
+ beforeAll(() => {
25
+ HTMLElement.prototype.scrollIntoView = vi.fn();
26
+ });
27
+
28
+ const table: DataTable = {
29
+ name: "users",
30
+ columns: [
31
+ {
32
+ name: "email",
33
+ type: "string",
34
+ external_type: "VARCHAR",
35
+ sample_values: [],
36
+ },
37
+ {
38
+ name: "name",
39
+ type: "string",
40
+ external_type: "VARCHAR",
41
+ sample_values: [],
42
+ },
43
+ ],
44
+ source: "memory",
45
+ source_type: "local",
46
+ type: "table",
47
+ engine: null,
48
+ indexes: null,
49
+ num_columns: 2,
50
+ num_rows: null,
51
+ variable_name: null,
52
+ primary_keys: null,
53
+ };
54
+
55
+ describe("Column actions", () => {
56
+ it("copies each column through one named button without expanding its preview", async () => {
57
+ const store = createStore();
58
+ const client = MockRequestClient.create();
59
+ store.set(requestClientAtom, client);
60
+ render(
61
+ <Provider store={store}>
62
+ <TooltipProvider>
63
+ <Command>
64
+ <CommandList>
65
+ <TableList tables={[table]} />
66
+ </CommandList>
67
+ </Command>
68
+ </TooltipProvider>
69
+ </Provider>,
70
+ );
71
+
72
+ fireEvent.click(await screen.findByText("users"));
73
+
74
+ for (const column of table.columns) {
75
+ const copyButton = await screen.findByRole("button", {
76
+ name: `Copy column name ${column.name}`,
77
+ });
78
+ const row = screen.getByRole("option", {
79
+ name: `${column.name} ${column.external_type}`,
80
+ });
81
+ expect(within(row).getAllByRole("button")).toEqual([copyButton]);
82
+ expect(copyButton.querySelector("button")).toBeNull();
83
+
84
+ fireEvent.focus(copyButton);
85
+ expect(
86
+ await screen.findByRole("tooltip", { name: "Copy column name" }),
87
+ ).toBeInTheDocument();
88
+
89
+ fireEvent.click(copyButton);
90
+ await waitFor(() => {
91
+ expect(copyToClipboard).toHaveBeenLastCalledWith(column.name);
92
+ });
93
+ expect(client.previewDatasetColumn).not.toHaveBeenCalled();
94
+ }
95
+ expect(copyToClipboard).toHaveBeenCalledTimes(2);
96
+ });
97
+ });
@@ -1,16 +1,24 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { render } from "@testing-library/react";
4
- import { Provider } from "jotai";
3
+ import { fireEvent, render, screen } from "@testing-library/react";
4
+ import { createStore, Provider } from "jotai";
5
5
  import type React from "react";
6
6
  import { beforeEach, describe, expect, it, vi } from "vitest";
7
7
  import { MockRequestClient } from "@/__mocks__/requests";
8
+ import { TooltipProvider } from "@/components/ui/tooltip";
8
9
  import type { SQLTableContext } from "@/core/datasets/data-source-connections";
9
10
  import type { DataTable, DataTableColumn } from "@/core/kernel/messages";
10
11
  import { requestClientAtom } from "@/core/network/requests";
11
12
  import { store } from "@/core/state/jotai";
12
13
  import { DatasetColumnPreview } from "../column-preview";
13
14
 
15
+ const { createNewCell } = vi.hoisted(() => ({ createNewCell: vi.fn() }));
16
+
17
+ vi.mock("@/core/cells/cells", async (importOriginal) => ({
18
+ ...(await importOriginal<typeof import("@/core/cells/cells")>()),
19
+ useCellActions: () => ({ createNewCell }),
20
+ }));
21
+
14
22
  const wrapper = ({ children }: { children: React.ReactNode }) => (
15
23
  <Provider store={store}>{children}</Provider>
16
24
  );
@@ -95,3 +103,56 @@ describe("DatasetColumnPreview fullyQualifiedTableName", () => {
95
103
  );
96
104
  });
97
105
  });
106
+
107
+ describe("DatasetColumnPreview chart actions", () => {
108
+ it("identifies each column and inserts its corresponding chart", () => {
109
+ const store = createStore();
110
+ store.set(requestClientAtom, MockRequestClient.create());
111
+ const columns: DataTableColumn[] = [
112
+ {
113
+ name: "email",
114
+ type: "string",
115
+ external_type: "str",
116
+ sample_values: [],
117
+ },
118
+ { name: "age", type: "integer", external_type: "int", sample_values: [] },
119
+ ];
120
+ render(
121
+ <Provider store={store}>
122
+ <TooltipProvider>
123
+ {columns.map((column) => (
124
+ <DatasetColumnPreview
125
+ key={column.name}
126
+ table={{ ...table, source_type: "local", engine: null }}
127
+ column={column}
128
+ onAddColumnChart={vi.fn()}
129
+ preview={{
130
+ table_name: table.name,
131
+ column_name: column.name,
132
+ chart_code: `chart_${column.name}`,
133
+ chart_spec: null,
134
+ error: null,
135
+ missing_packages: null,
136
+ stats: {},
137
+ }}
138
+ />
139
+ ))}
140
+ </TooltipProvider>
141
+ </Provider>,
142
+ );
143
+
144
+ for (const column of columns) {
145
+ fireEvent.click(
146
+ screen.getByRole("button", {
147
+ name: `Add chart to notebook for ${column.name}`,
148
+ }),
149
+ );
150
+ expect(createNewCell).toHaveBeenLastCalledWith({
151
+ code: `chart_${column.name}`,
152
+ before: false,
153
+ cellId: "__end__",
154
+ });
155
+ }
156
+ expect(createNewCell).toHaveBeenCalledTimes(2);
157
+ });
158
+ });