@marimo-team/frontend 0.16.0 → 0.16.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 (257) hide show
  1. package/dist/assets/ConnectedDataExplorerComponent-2wVcyvDj.js +19 -0
  2. package/dist/assets/{ImageComparisonComponent-fTHv1Ih0.js → ImageComparisonComponent-D2j6i0hv.js} +1 -1
  3. package/dist/assets/{VegaLite-Bdi-TyfY.js → VegaLite-BckFaf2D.js} +1 -1
  4. package/dist/assets/_baseEach-CvTX9w0Y.js +1 -0
  5. package/dist/assets/_baseMap-CtlwA90f.js +1 -0
  6. package/dist/assets/_baseUniq-BKktIGQ1.js +1 -0
  7. package/dist/assets/{_createAggregator-DcD0kTA5.js → _createAggregator-C5CVY-0t.js} +1 -1
  8. package/dist/assets/{agent-panel-Crv430aI.js → agent-panel-RGLNjkYe.js} +76 -57
  9. package/dist/assets/{any-language-editor-CQh552Wu.js → any-language-editor-DjuXwGCA.js} +1 -1
  10. package/dist/assets/{architectureDiagram-W76B3OCA-BAJeBxzt.js → architectureDiagram-W76B3OCA-Dyj4ds_R.js} +1 -1
  11. package/dist/assets/{between-horizontal-start-Boxgxbt_.js → between-horizontal-start-Dt2aKpPf.js} +1 -1
  12. package/dist/assets/{blockDiagram-QIGZ2CNN-CL-1svEK.js → blockDiagram-QIGZ2CNN-o-i7DDvN.js} +1 -1
  13. package/dist/assets/{c4Diagram-FPNF74CW-BbEqbCTl.js → c4Diagram-FPNF74CW-DGHEwWrx.js} +1 -1
  14. package/dist/assets/channel-Co6iMgWq.js +1 -0
  15. package/dist/assets/chat-panel-9alr8FS4.js +3 -0
  16. package/dist/assets/{chunk-4BX2VUAB-C--8TXeE.js → chunk-4BX2VUAB-BJecb-Ri.js} +1 -1
  17. package/dist/assets/{chunk-55IACEB6-Bj00HDqq.js → chunk-55IACEB6-CAATkc4w.js} +1 -1
  18. package/dist/assets/{chunk-FMBD7UC4-C-lhB6hN.js → chunk-FMBD7UC4-DPuNbQ-f.js} +1 -1
  19. package/dist/assets/{chunk-K7UQS3LO-B-pGTXPt.js → chunk-K7UQS3LO-C8TWVLiH.js} +1 -1
  20. package/dist/assets/{chunk-QN33PNHL-DqUzGhvm.js → chunk-QN33PNHL-DiZZ09q4.js} +1 -1
  21. package/dist/assets/{chunk-QZHKN3VN-TntJHfSk.js → chunk-QZHKN3VN-BIUM7usu.js} +1 -1
  22. package/dist/assets/{chunk-TVAH2DTR-HUJb1psV.js → chunk-TVAH2DTR-vGTArPBG.js} +1 -1
  23. package/dist/assets/{chunk-TZMSLE5B-BK3C__t3.js → chunk-TZMSLE5B-D2KRqp_x.js} +1 -1
  24. package/dist/assets/{circle-play-DBLOv1Yu.js → circle-play-cjeNez0N.js} +1 -1
  25. package/dist/assets/classDiagram-KNZD7YFC-BbJ0rY3y.js +1 -0
  26. package/dist/assets/classDiagram-v2-RKCZMP56-BbJ0rY3y.js +1 -0
  27. package/dist/assets/{clear-button-BeoFbEKH.js → clear-button-C97JtAez.js} +1 -1
  28. package/dist/assets/clone-BMP0PsTa.js +1 -0
  29. package/dist/assets/{command-palette-CXZiSv0I.js → command-palette-B93Pjcky.js} +1 -1
  30. package/dist/assets/{common-C7oJcmCT.js → common-Du9rSOwD.js} +1 -1
  31. package/dist/assets/{compile-7L0MwhyI.js → compile-CZXqyOxa.js} +1 -1
  32. package/dist/assets/{cose-bilkent-S5V4N54A-BMkGLcVC.js → cose-bilkent-S5V4N54A-CqUN5Y9b.js} +1 -1
  33. package/dist/assets/{dagre-5GWH7T2D-BJtRienS.js → dagre-5GWH7T2D-RJqTI9DM.js} +1 -1
  34. package/dist/assets/{data-grid-overlay-editor-DBkmGtNs.js → data-grid-overlay-editor-DZN0q1LV.js} +1 -1
  35. package/dist/assets/datasources-panel-v7H3cR0p.js +1 -0
  36. package/dist/assets/{dependency-graph-panel-DEdOxp2X.js → dependency-graph-panel-CEog_O7V.js} +1 -1
  37. package/dist/assets/{diagram-N5W7TBWH-CmECY3nb.js → diagram-N5W7TBWH-D-l4zZ9d.js} +1 -1
  38. package/dist/assets/{diagram-QEK2KX5R-DMOVSNKD.js → diagram-QEK2KX5R-CCOmBUt-.js} +1 -1
  39. package/dist/assets/{diagram-S2PKOQOG-BiJ96PNQ.js → diagram-S2PKOQOG-C_I_9jnZ.js} +1 -1
  40. package/dist/assets/{documentation-panel-xULhaEv3.js → documentation-panel-C1BtMZ3M.js} +1 -1
  41. package/dist/assets/edit-page-B-oevUZ9.js +129 -0
  42. package/dist/assets/{ellipsis-vertical-BBqXIlc2.js → ellipsis-vertical-BEb-J8z6.js} +1 -1
  43. package/dist/assets/{empty-state-B3dA3G5P.js → empty-state-C99UyDE3.js} +1 -1
  44. package/dist/assets/{erDiagram-AWTI2OKA-MP1DiFRo.js → erDiagram-AWTI2OKA-BePOLi5M.js} +1 -1
  45. package/dist/assets/{error-panel-Cc1sv-Ag.js → error-panel-Bs34jXFh.js} +1 -1
  46. package/dist/assets/file-explorer-panel-Ck6UL861.js +1 -0
  47. package/dist/assets/{flowDiagram-PVAE7QVJ-BX7caPp7.js → flowDiagram-PVAE7QVJ-BgjFu5l7.js} +1 -1
  48. package/dist/assets/{ganttDiagram-OWAHRB6G-B462g4Yf.js → ganttDiagram-OWAHRB6G-YOPb3XSV.js} +4 -4
  49. package/dist/assets/{gitGraphDiagram-NY62KEGX-CGgvZ9-9.js → gitGraphDiagram-NY62KEGX-CGhqaDTy.js} +1 -1
  50. package/dist/assets/{glide-data-editor-C0gUFZON.js → glide-data-editor-9QUH6iso.js} +11 -11
  51. package/dist/assets/{graph-CHRVBzY5.js → graph-DQQFGrho.js} +1 -1
  52. package/dist/assets/home-page-DRKpPCrF.js +9 -0
  53. package/dist/assets/{index-C1v_Z9et.js → index-2252nrk6.js} +1 -1
  54. package/dist/assets/{index-CQDrxQ0j.js → index-Aeo6WiK7.js} +1 -1
  55. package/dist/assets/{index-C4Tn5NvJ.js → index-B8jXZ12t.js} +1 -1
  56. package/dist/assets/{index-DoRmcrKM.js → index-BAbIIxHU.js} +1 -1
  57. package/dist/assets/{index-BY93Ejhl.js → index-BJNCMUmG.js} +1 -1
  58. package/dist/assets/{index-CpTPJo4k.js → index-BW3k9Gss.js} +1 -1
  59. package/dist/assets/{index-D1vmG6DS.js → index-BjgnbONl.js} +1 -1
  60. package/dist/assets/{index-z9bohSQJ.js → index-BprjMYH5.js} +1 -1
  61. package/dist/assets/{index-BVgAenPd.js → index-C1ez98sk.js} +1 -1
  62. package/dist/assets/{index-D9UKkrr2.js → index-C2MD0vgD.js} +1 -1
  63. package/dist/assets/index-C7CoaNFb.js +578 -0
  64. package/dist/assets/{index-C-GhZ7ti.js → index-CFKO7WXI.js} +1 -1
  65. package/dist/assets/{index-lYa_leQE.js → index-CUFv_thQ.js} +1 -1
  66. package/dist/assets/{index-vmICa5KN.js → index-C_tkBKNO.js} +1 -1
  67. package/dist/assets/{index-C77h_TXN.js → index-CfaDbEdi.js} +1 -1
  68. package/dist/assets/{index-DEQvTChO.js → index-ClzeQrN7.js} +1 -1
  69. package/dist/assets/index-DadI618h.css +1 -0
  70. package/dist/assets/{index-DRMm6SNo.js → index-DdnKZNxM.js} +1 -1
  71. package/dist/assets/{index-CWMgowgL.js → index-G5QZppK2.js} +1 -1
  72. package/dist/assets/{index-Clbi_Yaq.js → index-SGLNXrGP.js} +1 -1
  73. package/dist/assets/{index-C-8WADat.js → index-aE43R74q.js} +1 -1
  74. package/dist/assets/infoDiagram-STP46IZ2-CJLOpSAf.js +2 -0
  75. package/dist/assets/{isEmpty-DU_ogP_D.js → isEmpty-D-4c7sMv.js} +1 -1
  76. package/dist/assets/{journeyDiagram-BIP6EPQ6-C6EgLP_Q.js → journeyDiagram-BIP6EPQ6-C94u3Mv3.js} +1 -1
  77. package/dist/assets/{kanban-definition-6OIFK2YF-BXzYO1yj.js → kanban-definition-6OIFK2YF-BEXYFzz7.js} +1 -1
  78. package/dist/assets/{layout-jihVw5-i.js → layout-Bz2BJ2ru.js} +1 -1
  79. package/dist/assets/{linear-C4blANlC.js → linear-D8s7K76e.js} +1 -1
  80. package/dist/assets/links-BpXlz1GG.js +7 -0
  81. package/dist/assets/{logs-panel-D401qzZh.js → logs-panel-DC7wpmPz.js} +1 -1
  82. package/dist/assets/{markdown-renderer-Cd9eYyaL.js → markdown-renderer-DRdSWR9X.js} +20 -20
  83. package/dist/assets/{mermaid-BEVuRz_O.js → mermaid-Y3x4hmD0.js} +1 -1
  84. package/dist/assets/{mermaid.core-CaSnaLH0.js → mermaid.core-DzthE35Y.js} +4 -4
  85. package/dist/assets/min-BBO3-1Hg.js +1 -0
  86. package/dist/assets/{mindmap-definition-Q6HEUPPD-BXUM5MT2.js → mindmap-definition-Q6HEUPPD-DktvuLe1.js} +1 -1
  87. package/dist/assets/{number-overlay-editor-4uWXGlPG.js → number-overlay-editor-BEfwI1IT.js} +1 -1
  88. package/dist/assets/outline-panel-CdsnAy2w.js +1 -0
  89. package/dist/assets/{packages-panel-CJL0MVlj.js → packages-panel-DiTA-d_D.js} +1 -1
  90. package/dist/assets/{pieDiagram-ADFJNKIX-Dxt5PVNo.js → pieDiagram-ADFJNKIX-DQDNQ-de.js} +1 -1
  91. package/dist/assets/{quadrantDiagram-LMRXKWRM-D4pUaA31.js → quadrantDiagram-LMRXKWRM-0kgIXc2-.js} +1 -1
  92. package/dist/assets/{react-plotly-cJZ0VWBq.js → react-plotly-DJqqfM7c.js} +1 -1
  93. package/dist/assets/{requirementDiagram-4UW4RH46-DVRTjgas.js → requirementDiagram-4UW4RH46-B5rb0ypd.js} +1 -1
  94. package/dist/assets/{run-page-BUEnMC9w.js → run-page-CFmLrv1R.js} +1 -1
  95. package/dist/assets/{sankeyDiagram-GR3RE2ED-CVFnD9C-.js → sankeyDiagram-GR3RE2ED-Dom7IlnF.js} +1 -1
  96. package/dist/assets/{scratchpad-panel-BIgRENkI.js → scratchpad-panel-CuHWpHO8.js} +1 -1
  97. package/dist/assets/{secrets-panel-xY5-V_BD.js → secrets-panel-CfHc5YD0.js} +1 -1
  98. package/dist/assets/{sequenceDiagram-C3RYC4MD-_lY4ZN_S.js → sequenceDiagram-C3RYC4MD-PNJWXQbw.js} +1 -1
  99. package/dist/assets/{slides-component-DMjQomc3.css → slides-component-C-LoGC1U.css} +1 -1
  100. package/dist/assets/{slides-component-Xjymwj7X.js → slides-component-CJgaTRZ0.js} +1 -1
  101. package/dist/assets/snippets-panel-B2EC1txM.js +1 -0
  102. package/dist/assets/sortBy-DZnlX29-.js +1 -0
  103. package/dist/assets/{state-C4NiC9tO.js → state-CWict9RU.js} +1 -1
  104. package/dist/assets/{stateDiagram-KXAO66HF-Da0JQWCn.js → stateDiagram-KXAO66HF-BE58aJnr.js} +1 -1
  105. package/dist/assets/stateDiagram-v2-UMBNRL4Z-CdThjimL.js +1 -0
  106. package/dist/assets/storage-DRaR04wR.js +26 -0
  107. package/dist/assets/{terminal-BPwTkXae.js → terminal-BX3Su5q7.js} +1 -1
  108. package/dist/assets/{time-Dv5_Ouz_.js → time-hUzZfpNE.js} +1 -1
  109. package/dist/assets/{timeline-definition-XQNQX7LJ-Dxh5Zu2e.js → timeline-definition-XQNQX7LJ-CqQP9t51.js} +1 -1
  110. package/dist/assets/tracing-B10Q1n-L.js +2 -0
  111. package/dist/assets/{tracing-panel-DAzrzNmm.js → tracing-panel-Du8WCnno.js} +2 -2
  112. package/dist/assets/{trash-Dc6DSjz_.js → trash-B81GTiv6.js} +1 -1
  113. package/dist/assets/{tree-jheoerAX.js → tree-6vW2ogkh.js} +1 -1
  114. package/dist/assets/{treemap-75Q7IDZK-IgpxeGaf.js → treemap-75Q7IDZK-CdwDwwsz.js} +27 -27
  115. package/dist/assets/variable-panel-D5qgJI7k.js +1 -0
  116. package/dist/assets/{vega-component-BpfpiPKI.js → vega-component-DJaJWMJM.js} +1 -1
  117. package/dist/assets/worker-fHbtoWvT.js +1 -0
  118. package/dist/assets/{xychartDiagram-6GGTOJPD-CmNigJ31.js → xychartDiagram-6GGTOJPD-WFtXqaM9.js} +1 -1
  119. package/dist/index.html +2 -2
  120. package/package.json +3 -2
  121. package/src/components/app-config/user-config-form.tsx +46 -1
  122. package/src/components/chat/acp/__tests__/__snapshots__/prompt.test.ts.snap +62 -43
  123. package/src/components/chat/acp/__tests__/atoms.test.ts +1 -1
  124. package/src/components/chat/acp/__tests__/state.test.ts +36 -36
  125. package/src/components/chat/acp/agent-panel.tsx +24 -27
  126. package/src/components/chat/acp/blocks.tsx +6 -6
  127. package/src/components/chat/acp/prompt.ts +62 -43
  128. package/src/components/chat/chat-panel.tsx +5 -1
  129. package/src/components/chat/markdown-renderer.tsx +6 -10
  130. package/src/components/chat/tool-call-accordion.tsx +52 -20
  131. package/src/components/data-table/SearchBar.tsx +8 -7
  132. package/src/components/data-table/__tests__/column_formatting.test.ts +50 -35
  133. package/src/components/data-table/__tests__/data-table.test.tsx +39 -1
  134. package/src/components/data-table/cell-hover-template/feature.ts +14 -0
  135. package/src/components/data-table/cell-hover-template/types.ts +11 -0
  136. package/src/components/data-table/charts/components/form-fields.tsx +41 -37
  137. package/src/components/data-table/charts/forms/common-chart.tsx +2 -2
  138. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +5 -2
  139. package/src/components/data-table/column-formatting/feature.ts +62 -29
  140. package/src/components/data-table/column-formatting/types.ts +1 -0
  141. package/src/components/data-table/column-header.tsx +3 -1
  142. package/src/components/data-table/column-summary/chart-spec-model.tsx +24 -7
  143. package/src/components/data-table/column-summary/column-summary.tsx +18 -9
  144. package/src/components/data-table/columns.tsx +42 -18
  145. package/src/components/data-table/data-table.tsx +10 -2
  146. package/src/components/data-table/date-popover.tsx +85 -75
  147. package/src/components/data-table/filter-pills.tsx +14 -9
  148. package/src/components/data-table/header-items.tsx +5 -1
  149. package/src/components/data-table/pagination.tsx +20 -13
  150. package/src/components/data-table/renderers.tsx +28 -0
  151. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +10 -8
  152. package/src/components/datasources/column-preview.tsx +6 -2
  153. package/src/components/datasources/datasources.tsx +8 -12
  154. package/src/components/editor/ai/transport/chat-transport.tsx +4 -1
  155. package/src/components/editor/cell/CellStatus.tsx +23 -20
  156. package/src/components/editor/cell/CreateCellButton.tsx +3 -4
  157. package/src/components/editor/cell/code/language-toggle.tsx +3 -4
  158. package/src/components/editor/chrome/wrapper/footer-items/machine-stats.tsx +39 -28
  159. package/src/components/editor/controls/notebook-menu-dropdown.tsx +4 -2
  160. package/src/components/editor/file-tree/requesting-tree.tsx +14 -8
  161. package/src/components/editor/renderers/CellArray.tsx +3 -4
  162. package/src/components/editor/renderers/slides-layout/slides-layout.tsx +3 -3
  163. package/src/components/editor/renderers/slides-layout/types.ts +1 -0
  164. package/src/components/pages/home-page.tsx +4 -1
  165. package/src/components/slides/slides-component.tsx +1 -1
  166. package/src/components/slides/slides.css +6 -0
  167. package/src/components/terminal/theme.tsx +1 -0
  168. package/src/components/tracing/tracing-spec.ts +5 -4
  169. package/src/components/ui/range-slider.tsx +4 -2
  170. package/src/components/ui/slider.tsx +3 -1
  171. package/src/components/variables/variables-table.tsx +3 -0
  172. package/src/core/MarimoApp.tsx +9 -6
  173. package/src/core/ai/context/__tests__/registry.test.ts +6 -4
  174. package/src/core/ai/context/providers/cell-output.ts +3 -2
  175. package/src/core/ai/context/providers/error.ts +3 -1
  176. package/src/core/ai/context/providers/file.ts +7 -2
  177. package/src/core/ai/context/providers/tables.ts +3 -2
  178. package/src/core/ai/context/providers/variable.ts +6 -4
  179. package/src/core/ai/staged-cells.ts +34 -1
  180. package/src/core/cells/__tests__/add-missing-import.test.ts +67 -22
  181. package/src/core/cells/add-missing-import.ts +24 -7
  182. package/src/core/cells/cells.ts +26 -27
  183. package/src/core/cells/logs.ts +1 -1
  184. package/src/core/codemirror/find-replace/search-highlight.ts +3 -1
  185. package/src/core/codemirror/language/LanguageAdapters.ts +9 -3
  186. package/src/core/codemirror/lsp/notebook-lsp.ts +8 -2
  187. package/src/core/codemirror/readonly/__tests__/extension.test.ts +1 -1
  188. package/src/core/codemirror/rtc/loro/awareness.ts +52 -17
  189. package/src/core/codemirror/rtc/loro/sync.ts +12 -4
  190. package/src/core/config/config-schema.ts +1 -0
  191. package/src/core/config/config.ts +4 -0
  192. package/src/core/hotkeys/hotkeys.ts +8 -4
  193. package/src/core/i18n/__tests__/locale-provider.test.tsx +176 -0
  194. package/src/core/i18n/locale-provider.tsx +35 -0
  195. package/src/core/i18n/with-locale.tsx +12 -0
  196. package/src/core/islands/components/web-components.tsx +13 -10
  197. package/src/core/kernel/RuntimeState.ts +4 -1
  198. package/src/core/kernel/messages.ts +2 -2
  199. package/src/core/network/DeferredRequestRegistry.ts +16 -4
  200. package/src/core/runtime/runtime.ts +5 -4
  201. package/src/core/wasm/bridge.ts +5 -1
  202. package/src/core/wasm/store.ts +4 -1
  203. package/src/core/wasm/worker/message-buffer.ts +3 -2
  204. package/src/core/websocket/types.ts +22 -16
  205. package/src/hooks/useFormatting.ts +97 -0
  206. package/src/hooks/useTimer.ts +8 -5
  207. package/src/plugins/core/registerReactComponent.tsx +16 -10
  208. package/src/plugins/impl/DataTablePlugin.tsx +4 -0
  209. package/src/plugins/impl/RangeSliderPlugin.tsx +5 -3
  210. package/src/plugins/impl/SliderPlugin.tsx +3 -1
  211. package/src/plugins/impl/anywidget/model.ts +16 -5
  212. package/src/plugins/impl/data-editor/types.ts +7 -5
  213. package/src/plugins/impl/data-explorer/components/column-summary.tsx +20 -13
  214. package/src/plugins/impl/panel/utils.ts +6 -4
  215. package/src/plugins/layout/OutlinePlugin.tsx +69 -0
  216. package/src/plugins/layout/StatPlugin.tsx +4 -1
  217. package/src/plugins/plugins.ts +2 -0
  218. package/src/utils/__tests__/dates.test.ts +45 -24
  219. package/src/utils/__tests__/numbers.test.ts +42 -30
  220. package/src/utils/__tests__/once.test.ts +187 -0
  221. package/src/utils/dates.ts +15 -10
  222. package/src/utils/edit-distance.ts +8 -6
  223. package/src/utils/errors.ts +1 -1
  224. package/src/utils/id-tree.tsx +21 -10
  225. package/src/utils/localStorage.ts +13 -4
  226. package/src/utils/numbers.ts +11 -11
  227. package/src/utils/once.ts +32 -0
  228. package/src/utils/paths.ts +4 -1
  229. package/src/utils/pluralize.ts +12 -5
  230. package/src/utils/python-poet/poet.ts +30 -15
  231. package/src/utils/time.ts +5 -1
  232. package/dist/assets/ConnectedDataExplorerComponent-BErMbWvG.js +0 -19
  233. package/dist/assets/_baseEach-CNBxBxvS.js +0 -1
  234. package/dist/assets/_baseMap-D1WHjKrd.js +0 -1
  235. package/dist/assets/_baseUniq-CCgDNtZb.js +0 -1
  236. package/dist/assets/channel-_2eNSz0n.js +0 -1
  237. package/dist/assets/chat-panel-CXh5Wl6C.js +0 -3
  238. package/dist/assets/classDiagram-KNZD7YFC-BGmh9POF.js +0 -1
  239. package/dist/assets/classDiagram-v2-RKCZMP56-BGmh9POF.js +0 -1
  240. package/dist/assets/clone-BFDSPAj3.js +0 -1
  241. package/dist/assets/datasources-panel-B7FbYLiy.js +0 -1
  242. package/dist/assets/edit-page-BrYda9VE.js +0 -129
  243. package/dist/assets/file-explorer-panel-Bw59Kva1.js +0 -1
  244. package/dist/assets/home-page-Fb2osjys.js +0 -9
  245. package/dist/assets/index-Cx0bsY1w.css +0 -1
  246. package/dist/assets/index-DKEudB02.js +0 -578
  247. package/dist/assets/infoDiagram-STP46IZ2-CVyrdLc8.js +0 -2
  248. package/dist/assets/links-D59GIweI.js +0 -7
  249. package/dist/assets/min-DUMu_zeK.js +0 -1
  250. package/dist/assets/outline-panel-DIzkvm2I.js +0 -1
  251. package/dist/assets/snippets-panel-CTPYW41n.js +0 -1
  252. package/dist/assets/sortBy-BNZKwiq_.js +0 -1
  253. package/dist/assets/stateDiagram-v2-UMBNRL4Z-D5lYZOOt.js +0 -1
  254. package/dist/assets/storage-CMdLzB_c.js +0 -26
  255. package/dist/assets/tracing-BCIurUfa.js +0 -2
  256. package/dist/assets/variable-panel-DYAiLBmF.js +0 -1
  257. package/dist/assets/worker-X5rxzQGQ.js +0 -1
@@ -19,11 +19,13 @@ import {
19
19
  useReactTable,
20
20
  } from "@tanstack/react-table";
21
21
  import React, { memo } from "react";
22
+ import { useLocale } from "react-aria";
22
23
 
23
24
  import { Table } from "@/components/ui/table";
24
25
  import type { GetRowIds } from "@/plugins/impl/DataTablePlugin";
25
26
  import { cn } from "@/utils/cn";
26
27
  import type { PanelType } from "../editor/chrome/panels/context-aware-panel/context-aware-panel";
28
+ import { CellHoverTemplateFeature } from "./cell-hover-template/feature";
27
29
  import { CellSelectionFeature } from "./cell-selection/feature";
28
30
  import type { CellSelectionState } from "./cell-selection/types";
29
31
  import { CellStylingFeature } from "./cell-styling/feature";
@@ -63,6 +65,7 @@ interface DataTableProps<TData> extends Partial<DownloadActionProps> {
63
65
  rowSelection?: RowSelectionState;
64
66
  cellSelection?: CellSelectionState;
65
67
  cellStyling?: CellStyleState | null;
68
+ hoverTemplate?: string | null;
66
69
  onRowSelectionChange?: OnChangeFn<RowSelectionState>;
67
70
  onCellSelectionChange?: OnChangeFn<CellSelectionState>;
68
71
  getRowIds?: GetRowIds;
@@ -103,6 +106,7 @@ const DataTableInternal = <TData,>({
103
106
  rowSelection,
104
107
  cellSelection,
105
108
  cellStyling,
109
+ hoverTemplate,
106
110
  paginationState,
107
111
  setPaginationState,
108
112
  downloadAs,
@@ -131,6 +135,7 @@ const DataTableInternal = <TData,>({
131
135
  }: DataTableProps<TData>) => {
132
136
  const [isSearchEnabled, setIsSearchEnabled] = React.useState<boolean>(false);
133
137
  const [showLoadingBar, setShowLoadingBar] = React.useState<boolean>(false);
138
+ const { locale } = useLocale();
134
139
 
135
140
  const { columnPinning, setColumnPinning } = useColumnPinning(
136
141
  freezeColumnsLeft,
@@ -176,6 +181,7 @@ const DataTableInternal = <TData,>({
176
181
  ColumnFormattingFeature,
177
182
  CellSelectionFeature,
178
183
  CellStylingFeature,
184
+ CellHoverTemplateFeature,
179
185
  CopyColumnFeature,
180
186
  FocusRowFeature,
181
187
  ],
@@ -199,6 +205,7 @@ const DataTableInternal = <TData,>({
199
205
  },
200
206
  }
201
207
  : {}),
208
+ locale: locale,
202
209
  manualPagination: manualPagination,
203
210
  getPaginationRowModel: getPaginationRowModel(),
204
211
  // sorting
@@ -233,10 +240,11 @@ const DataTableInternal = <TData,>({
233
240
  ? {}
234
241
  : // No pagination, show all rows
235
242
  { pagination: { pageIndex: 0, pageSize: data.length } }),
236
- rowSelection,
237
- cellSelection,
243
+ rowSelection: rowSelection ?? {},
244
+ cellSelection: cellSelection ?? [],
238
245
  cellStyling,
239
246
  columnPinning: columnPinning,
247
+ cellHoverTemplate: hoverTemplate,
240
248
  },
241
249
  });
242
250
 
@@ -1,5 +1,6 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
2
  import React from "react";
3
+ import { useDateFormatter, useLocale } from "react-aria";
3
4
  import { Tooltip } from "@/components/ui/tooltip";
4
5
 
5
6
  interface DatePopoverProps {
@@ -19,33 +20,17 @@ export const DatePopover: React.FC<DatePopoverProps> = ({
19
20
  }
20
21
 
21
22
  const dateObj = new Date(date);
22
- const relativeTime = getRelativeTime(dateObj);
23
23
 
24
24
  const content = (
25
25
  <div className="min-w-[240px] p-1 text-sm">
26
- <div className="text-muted-foreground mb-2">{relativeTime}</div>
26
+ <div className="text-muted-foreground mb-2">
27
+ <RelativeTime date={dateObj} />
28
+ </div>
27
29
  <div className="space-y-1">
28
30
  {type === "datetime" ? (
29
- Object.entries(getTimezones(dateObj)).map(
30
- ([timezone, formattedDate]) => (
31
- <div
32
- key={timezone}
33
- className="grid grid-cols-[fit-content(40px)_1fr] gap-4 items-center justify-items-end"
34
- >
35
- <span className="bg-muted rounded-md py-1 px-2 w-fit ml-auto">
36
- {timezone}
37
- </span>
38
- <span>{formattedDate}</span>
39
- </div>
40
- ),
41
- )
31
+ <TimezoneDisplay date={dateObj} />
42
32
  ) : (
43
- <span>
44
- {dateObj.toLocaleDateString("en-US", {
45
- timeZone: "UTC",
46
- dateStyle: "long",
47
- })}
48
- </span>
33
+ <DateDisplay date={dateObj} />
49
34
  )}
50
35
  </div>
51
36
  </div>
@@ -58,57 +43,79 @@ export const DatePopover: React.FC<DatePopoverProps> = ({
58
43
  );
59
44
  };
60
45
 
61
- function getTimezones(date: Date) {
62
- const localTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
63
-
46
+ const TimezoneDisplay = ({ date }: { date: Date }) => {
47
+ const { locale } = useLocale();
48
+ const localTimezone = Intl.DateTimeFormat(locale).resolvedOptions().timeZone;
64
49
  const hasSubSeconds = date.getUTCMilliseconds() !== 0;
65
- if (hasSubSeconds) {
66
- return {
67
- UTC: new Intl.DateTimeFormat("en-US", {
68
- timeZone: "UTC",
69
- year: "numeric",
70
- month: "2-digit",
71
- day: "2-digit",
72
- hour: "2-digit",
73
- minute: "2-digit",
74
- second: "2-digit",
75
- fractionalSecondDigits: 3,
76
- }).format(date),
77
- [localTimezone]: new Intl.DateTimeFormat("en-US", {
78
- timeZone: localTimezone,
79
- year: "numeric",
80
- month: "2-digit",
81
- day: "2-digit",
82
- hour: "2-digit",
83
- minute: "2-digit",
84
- second: "2-digit",
85
- fractionalSecondDigits: 3,
86
- }).format(date),
87
- };
88
- }
89
50
 
90
- return {
91
- UTC: new Intl.DateTimeFormat("en-US", {
92
- timeZone: "UTC",
93
- dateStyle: "long",
94
- timeStyle: "medium",
95
- }).format(date),
96
- [localTimezone]: new Intl.DateTimeFormat("en-US", {
97
- timeZone: localTimezone,
98
- dateStyle: "long",
99
- timeStyle: "medium",
100
- }).format(date),
101
- };
102
- }
51
+ const utcFormatter = useDateFormatter(
52
+ hasSubSeconds
53
+ ? {
54
+ timeZone: "UTC",
55
+ year: "numeric",
56
+ month: "2-digit",
57
+ day: "2-digit",
58
+ hour: "2-digit",
59
+ minute: "2-digit",
60
+ second: "2-digit",
61
+ fractionalSecondDigits: 3,
62
+ }
63
+ : {
64
+ timeZone: "UTC",
65
+ dateStyle: "long",
66
+ timeStyle: "medium",
67
+ },
68
+ );
69
+
70
+ const localFormatter = useDateFormatter(
71
+ hasSubSeconds
72
+ ? {
73
+ timeZone: localTimezone,
74
+ year: "numeric",
75
+ month: "2-digit",
76
+ day: "2-digit",
77
+ hour: "2-digit",
78
+ minute: "2-digit",
79
+ second: "2-digit",
80
+ fractionalSecondDigits: 3,
81
+ }
82
+ : {
83
+ timeZone: localTimezone,
84
+ dateStyle: "long",
85
+ timeStyle: "medium",
86
+ },
87
+ );
88
+
89
+ return (
90
+ <>
91
+ <div className="grid grid-cols-[fit-content(40px)_1fr] gap-4 items-center justify-items-end">
92
+ <span className="bg-muted rounded-md py-1 px-2 w-fit ml-auto">UTC</span>
93
+ <span>{utcFormatter.format(date)}</span>
94
+ </div>
95
+ <div className="grid grid-cols-[fit-content(40px)_1fr] gap-4 items-center justify-items-end">
96
+ <span className="bg-muted rounded-md py-1 px-2 w-fit ml-auto">
97
+ {localTimezone}
98
+ </span>
99
+ <span>{localFormatter.format(date)}</span>
100
+ </div>
101
+ </>
102
+ );
103
+ };
104
+
105
+ const DateDisplay = ({ date }: { date: Date }) => {
106
+ const dateFormatter = useDateFormatter({
107
+ timeZone: "UTC",
108
+ dateStyle: "long",
109
+ });
103
110
 
104
- /**
105
- * Converts a date into a human-readable relative time string (e.g. "2 hours ago", "in 5 minutes")
106
- * Uses Intl.RelativeTimeFormat for localized formatting
107
- */
108
- function getRelativeTime(date: Date): string {
109
- // Initialize relative time formatter with English locale and "auto" numeric style
110
- // "auto" allows for strings like "yesterday" instead of "1 day ago"
111
- const relativeTimeFormatter = new Intl.RelativeTimeFormat("en", {
111
+ return <span>{dateFormatter.format(date)}</span>;
112
+ };
113
+
114
+ const RelativeTime = ({ date }: { date: Date }) => {
115
+ const { locale } = useLocale();
116
+
117
+ // Initialize relative time formatter with current locale and "auto" numeric style
118
+ const relativeTimeFormatter = new Intl.RelativeTimeFormat(locale, {
112
119
  numeric: "auto",
113
120
  });
114
121
 
@@ -117,7 +124,6 @@ function getRelativeTime(date: Date): string {
117
124
  const differenceInSeconds = (currentTime.getTime() - date.getTime()) / 1000;
118
125
 
119
126
  // Define time units with their thresholds and conversion factors
120
- // Format: [threshold before next unit, seconds in this unit, unit name]
121
127
  const timeUnits: Array<[number, number, string]> = [
122
128
  [60, 1, "second"], // Less than 60 seconds
123
129
  [60, 60, "minute"], // Less than 60 minutes
@@ -134,13 +140,17 @@ function getRelativeTime(date: Date): string {
134
140
  // Convert to fixed decimal and negate since RelativeTimeFormat expects
135
141
  // negative values for past times and positive for future times
136
142
  const roundedValue = -Number(valueInUnits.toFixed(1));
137
- return relativeTimeFormatter.format(
138
- roundedValue,
139
- unitName as Intl.RelativeTimeFormatUnit,
143
+ return (
144
+ <span>
145
+ {relativeTimeFormatter.format(
146
+ roundedValue,
147
+ unitName as Intl.RelativeTimeFormatUnit,
148
+ )}
149
+ </span>
140
150
  );
141
151
  }
142
152
  }
143
153
 
144
154
  // Should never reach here due to Infinity threshold, but provide fallback
145
- return relativeTimeFormatter.format(0, "second");
146
- }
155
+ return <span>{relativeTimeFormatter.format(0, "second")}</span>;
156
+ };
@@ -7,6 +7,7 @@ import type {
7
7
  Table,
8
8
  } from "@tanstack/react-table";
9
9
  import { XIcon } from "lucide-react";
10
+ import { type DateFormatter, useDateFormatter } from "react-aria";
10
11
  import { logNever } from "@/utils/assertNever";
11
12
  import { Badge } from "../ui/badge";
12
13
  import type { ColumnFilterValue } from "./filters";
@@ -18,12 +19,21 @@ interface Props<TData> {
18
19
  }
19
20
 
20
21
  export const FilterPills = <TData,>({ filters, table }: Props<TData>) => {
22
+ const timeFormatter = useDateFormatter({
23
+ hour: "2-digit",
24
+ minute: "2-digit",
25
+ second: "2-digit",
26
+ });
27
+
21
28
  if (!filters || filters.length === 0) {
22
29
  return null;
23
30
  }
24
31
 
25
32
  function renderFilterPill(filter: ColumnFilter) {
26
- const formattedValue = formatValue(filter.value as ColumnFilterValue);
33
+ const formattedValue = formatValue(
34
+ filter.value as ColumnFilterValue,
35
+ timeFormatter,
36
+ );
27
37
  if (!formattedValue) {
28
38
  return null;
29
39
  }
@@ -52,7 +62,7 @@ export const FilterPills = <TData,>({ filters, table }: Props<TData>) => {
52
62
  );
53
63
  };
54
64
 
55
- function formatValue(value: ColumnFilterValue) {
65
+ function formatValue(value: ColumnFilterValue, timeFormatter: DateFormatter) {
56
66
  if (!("type" in value)) {
57
67
  return;
58
68
  }
@@ -71,14 +81,9 @@ function formatValue(value: ColumnFilterValue) {
71
81
  return formatMinMax(value.min?.toISOString(), value.max?.toISOString());
72
82
  }
73
83
  if (value.type === "time") {
74
- const formatTime = new Intl.DateTimeFormat("en-US", {
75
- hour: "2-digit",
76
- minute: "2-digit",
77
- second: "2-digit",
78
- });
79
84
  return formatMinMax(
80
- value.min ? formatTime.format(value.min) : undefined,
81
- value.max ? formatTime.format(value.max) : undefined,
85
+ value.min ? timeFormatter.format(value.min) : undefined,
86
+ value.max ? timeFormatter.format(value.max) : undefined,
82
87
  );
83
88
  }
84
89
  if (value.type === "datetime") {
@@ -34,6 +34,7 @@ import { NAMELESS_COLUMN_PREFIX } from "./columns";
34
34
 
35
35
  export function renderFormatOptions<TData, TValue>(
36
36
  column: Column<TData, TValue>,
37
+ locale: string,
37
38
  ) {
38
39
  const dataType: DataType | undefined = column.columnDef.meta?.dataType;
39
40
  const columnFormatOptions = dataType ? formatOptions[dataType] : [];
@@ -51,6 +52,9 @@ export function renderFormatOptions<TData, TValue>(
51
52
  </DropdownMenuSubTrigger>
52
53
  <DropdownMenuPortal>
53
54
  <DropdownMenuSubContent>
55
+ <div className="text-xs text-muted-foreground px-2 py-1">
56
+ Locale: {locale}
57
+ </div>
54
58
  {Boolean(currentFormat) && (
55
59
  <>
56
60
  <DropdownMenuItem
@@ -72,7 +76,7 @@ export function renderFormatOptions<TData, TValue>(
72
76
  {option}
73
77
  </span>
74
78
  <span className="ml-auto pl-5 text-xs text-muted-foreground">
75
- {formattingExample(option)}
79
+ {formattingExample(option, locale)}
76
80
  </span>
77
81
  </DropdownMenuItem>
78
82
  ))}
@@ -9,8 +9,10 @@ import {
9
9
  ChevronsLeft,
10
10
  ChevronsRight,
11
11
  } from "lucide-react";
12
+ import { useLocale } from "react-aria";
12
13
  import { Button } from "@/components/ui/button";
13
14
  import { Events } from "@/utils/events";
15
+ import { prettyNumber } from "@/utils/numbers";
14
16
  import { PluralWord } from "@/utils/pluralize";
15
17
  import {
16
18
  Select,
@@ -40,6 +42,8 @@ export const DataTablePagination = <TData,>({
40
42
  tableLoading,
41
43
  showPageSizeSelector,
42
44
  }: DataTablePaginationProps<TData>) => {
45
+ const { locale } = useLocale();
46
+
43
47
  const renderTotal = () => {
44
48
  const { rowSelection, cellSelection } = table.getState();
45
49
  let selected = Object.keys(rowSelection).length;
@@ -58,7 +62,7 @@ export const DataTablePagination = <TData,>({
58
62
  if (isAllPageSelected && !isAllSelected) {
59
63
  return (
60
64
  <>
61
- <span>{prettyNumber(selected)} selected</span>
65
+ <span>{prettyNumber(selected, locale)} selected</span>
62
66
  <Button
63
67
  size="xs"
64
68
  data-testid="select-all-button"
@@ -73,7 +77,7 @@ export const DataTablePagination = <TData,>({
73
77
  }
74
78
  }}
75
79
  >
76
- Select all {prettyNumber(numRows)}
80
+ Select all {prettyNumber(numRows, locale)}
77
81
  </Button>
78
82
  </>
79
83
  );
@@ -82,7 +86,7 @@ export const DataTablePagination = <TData,>({
82
86
  if (selected) {
83
87
  return (
84
88
  <>
85
- <span>{prettyNumber(selected)} selected</span>
89
+ <span>{prettyNumber(selected, locale)} selected</span>
86
90
  <Button
87
91
  size="xs"
88
92
  data-testid="clear-selection-button"
@@ -107,7 +111,11 @@ export const DataTablePagination = <TData,>({
107
111
  );
108
112
  }
109
113
 
110
- const rowColumnCount = prettifyRowColumnCount(numRows, totalColumns);
114
+ const rowColumnCount = prettifyRowColumnCount(
115
+ numRows,
116
+ totalColumns,
117
+ locale,
118
+ );
111
119
  return <span>{rowColumnCount}</span>;
112
120
  };
113
121
  const currentPage = Math.min(
@@ -199,7 +207,9 @@ export const DataTablePagination = <TData,>({
199
207
  handlePageChange(() => table.setPageIndex(page))
200
208
  }
201
209
  />
202
- <span className="shrink-0">of {prettyNumber(totalPages)}</span>
210
+ <span className="shrink-0">
211
+ of {prettyNumber(totalPages, locale)}
212
+ </span>
203
213
  </div>
204
214
  <Button
205
215
  size="xs"
@@ -232,10 +242,6 @@ export const DataTablePagination = <TData,>({
232
242
  );
233
243
  };
234
244
 
235
- function prettyNumber(value: number): string {
236
- return new Intl.NumberFormat().format(value);
237
- }
238
-
239
245
  export const PageSelector = ({
240
246
  currentPage,
241
247
  totalPages,
@@ -313,17 +319,18 @@ export const PageSelector = ({
313
319
  );
314
320
  };
315
321
 
316
- export function prettifyRowCount(rowCount: number): string {
317
- return `${prettyNumber(rowCount)} ${new PluralWord("row").pluralize(rowCount)}`;
322
+ export function prettifyRowCount(rowCount: number, locale: string): string {
323
+ return `${prettyNumber(rowCount, locale)} ${new PluralWord("row").pluralize(rowCount)}`;
318
324
  }
319
325
 
320
326
  export const prettifyRowColumnCount = (
321
327
  numRows: number | "too_many",
322
328
  totalColumns: number,
329
+ locale: string,
323
330
  ): string => {
324
331
  const rowsLabel =
325
- numRows === "too_many" ? "Unknown" : prettifyRowCount(numRows);
326
- const columnsLabel = `${prettyNumber(totalColumns)} ${new PluralWord("column").pluralize(totalColumns)}`;
332
+ numRows === "too_many" ? "Unknown" : prettifyRowCount(numRows, locale);
333
+ const columnsLabel = `${prettyNumber(totalColumns, locale)} ${new PluralWord("column").pluralize(totalColumns)}`;
327
334
 
328
335
  return [rowsLabel, columnsLabel].join(", ");
329
336
  };
@@ -93,6 +93,25 @@ export const DataTableBody = <TData,>({
93
93
  handleCellsKeyDown,
94
94
  } = useCellRangeSelection({ table });
95
95
 
96
+ function applyHoverTemplate(
97
+ template: string,
98
+ cells: Array<Cell<TData, unknown>>,
99
+ ): string {
100
+ const variableRegex = /{{(\w+)}}/g;
101
+ // Map column id -> stringified value
102
+ const idToValue = new Map<string, string>();
103
+ for (const c of cells) {
104
+ const v = c.getValue();
105
+ // Prefer empty string for nulls to keep tooltip clean
106
+ const s = renderUnknownValue({ value: v, nullAsEmptyString: true });
107
+ idToValue.set(c.column.id, s);
108
+ }
109
+ return template.replace(variableRegex, (_substr, varName: string) => {
110
+ const val = idToValue.get(varName);
111
+ return val !== undefined ? val : `{{${varName}}}`;
112
+ });
113
+ }
114
+
96
115
  const renderCells = (cells: Array<Cell<TData, unknown>>) => {
97
116
  return cells.map((cell) => {
98
117
  const { className, style: pinningstyle } = getPinningStyles(cell.column);
@@ -101,6 +120,7 @@ export const DataTableBody = <TData,>({
101
120
  cell.getUserStyling?.() || {},
102
121
  pinningstyle,
103
122
  );
123
+
104
124
  return (
105
125
  <TableCell
106
126
  tabIndex={0}
@@ -145,10 +165,18 @@ export const DataTableBody = <TData,>({
145
165
  const isRowViewedInPanel =
146
166
  rowViewerPanelOpen && viewedRowIdx === rowIndex;
147
167
 
168
+ // Compute hover title once per row using this row's cells (visible or hidden)
169
+ const hoverTemplate = table.getState().cellHoverTemplate || null;
170
+ const rowCells = row.getAllCells();
171
+ const rowTitle = hoverTemplate
172
+ ? applyHoverTemplate(hoverTemplate, rowCells)
173
+ : undefined;
174
+
148
175
  return (
149
176
  <TableRow
150
177
  key={row.id}
151
178
  data-state={row.getIsSelected() && "selected"}
179
+ title={rowTitle}
152
180
  // These classes ensure that empty rows (nulls) still render
153
181
  className={cn(
154
182
  "border-t h-6",
@@ -15,6 +15,7 @@ import {
15
15
  SearchIcon,
16
16
  } from "lucide-react";
17
17
  import { useRef, useState } from "react";
18
+ import { useLocale } from "react-aria";
18
19
  import { ColumnName } from "@/components/datasources/components";
19
20
  import { CopyClipboardIcon } from "@/components/icons/copy-icon";
20
21
  import { KeyboardHotkeys } from "@/components/shortcuts/renderShortcut";
@@ -66,6 +67,7 @@ export const RowViewerPanel: React.FC<RowViewerPanelProps> = ({
66
67
  const [searchQuery, setSearchQuery] = useState("");
67
68
  const panelRef = useRef<HTMLDivElement>(null);
68
69
  const searchInputRef = useRef<HTMLInputElement>(null);
70
+ const { locale } = useLocale();
69
71
 
70
72
  const tooManyRows = totalRows === TOO_MANY_ROWS;
71
73
 
@@ -204,13 +206,13 @@ export const RowViewerPanel: React.FC<RowViewerPanelProps> = ({
204
206
  applyColumnFormatting: (value) => value,
205
207
  } as Column<unknown>;
206
208
 
207
- const cellContent = renderCellValue(
208
- mockColumn,
209
- () => columnValue,
210
- () => columnValue,
211
- undefined,
212
- "text-left break-word",
213
- );
209
+ const cellContent = renderCellValue({
210
+ column: mockColumn,
211
+ renderValue: () => columnValue,
212
+ getValue: () => columnValue,
213
+ selectCell: undefined,
214
+ cellStyles: "text-left break-word",
215
+ });
214
216
 
215
217
  const copyValue =
216
218
  typeof columnValue === "object"
@@ -286,7 +288,7 @@ export const RowViewerPanel: React.FC<RowViewerPanelProps> = ({
286
288
  <span className="text-xs">
287
289
  {tooManyRows
288
290
  ? `Row ${rowIdx + 1}`
289
- : `Row ${rowIdx + 1} of ${prettifyRowCount(totalRows)}`}
291
+ : `Row ${rowIdx + 1} of ${prettifyRowCount(totalRows, locale)}`}
290
292
  </span>
291
293
  <Button
292
294
  variant="outline"
@@ -3,6 +3,7 @@
3
3
  import { useAtomValue } from "jotai";
4
4
  import { PlusSquareIcon } from "lucide-react";
5
5
  import React, { Suspense } from "react";
6
+ import { useLocale } from "react-aria";
6
7
  import { maybeAddAltairImport } from "@/core/cells/add-missing-import";
7
8
  import { useCellActions } from "@/core/cells/cells";
8
9
  import { useLastFocusedCellId } from "@/core/cells/focus";
@@ -42,6 +43,7 @@ export const DatasetColumnPreview: React.FC<{
42
43
  }> = ({ table, column, preview, onAddColumnChart, sqlTableContext }) => {
43
44
  const { theme } = useTheme();
44
45
  const { previewDatasetColumn } = useRequestClient();
46
+ const { locale } = useLocale();
45
47
 
46
48
  const previewColumn = () => {
47
49
  previewDatasetColumn({
@@ -107,7 +109,8 @@ export const DatasetColumnPreview: React.FC<{
107
109
  refetchPreview: previewColumn,
108
110
  });
109
111
 
110
- const stats = preview.stats && renderStats(preview.stats, column.type);
112
+ const stats =
113
+ preview.stats && renderStats(preview.stats, column.type, locale);
111
114
 
112
115
  const chart = preview.chart_spec && renderChart(preview.chart_spec, theme);
113
116
 
@@ -175,6 +178,7 @@ export function renderPreviewError({
175
178
  export function renderStats(
176
179
  stats: Partial<Record<ColumnHeaderStatsKey, unknown>>,
177
180
  dataType: DataType,
181
+ locale: string,
178
182
  ) {
179
183
  return (
180
184
  <div className="gap-x-16 gap-y-1 grid grid-cols-2-fit border rounded p-2 empty:hidden">
@@ -189,7 +193,7 @@ export function renderStats(
189
193
  {convertStatsName(key as ColumnHeaderStatsKey, dataType)}
190
194
  </span>
191
195
  <span className="text-xs font-bold text-muted-foreground tracking-wide">
192
- {prettyNumber(value)}
196
+ {prettyNumber(value, locale)}
193
197
  </span>
194
198
  <CopyClipboardIcon
195
199
  className="h-3 w-3 invisible group-hover:visible"
@@ -304,10 +304,12 @@ const DatabaseItem: React.FC<{
304
304
  }> = ({ hasSearch, engineName, database, children }) => {
305
305
  const [isExpanded, setIsExpanded] = React.useState(false);
306
306
  const [isSelected, setIsSelected] = React.useState(false);
307
+ const [prevHasSearch, setPrevHasSearch] = React.useState(hasSearch);
307
308
 
308
- React.useEffect(() => {
309
+ if (prevHasSearch !== hasSearch) {
310
+ setPrevHasSearch(hasSearch);
309
311
  setIsExpanded(hasSearch);
310
- }, [hasSearch]);
312
+ }
311
313
 
312
314
  return (
313
315
  <>
@@ -399,14 +401,10 @@ const SchemaItem: React.FC<{
399
401
  children: React.ReactNode;
400
402
  hasSearch: boolean;
401
403
  }> = ({ databaseName, schema, children, hasSearch }) => {
402
- const [isExpanded, setIsExpanded] = React.useState(false);
404
+ const [isExpanded, setIsExpanded] = React.useState(hasSearch);
403
405
  const [isSelected, setIsSelected] = React.useState(false);
404
406
  const uniqueValue = `${databaseName}:${schema.name}`;
405
407
 
406
- React.useEffect(() => {
407
- setIsExpanded(hasSearch);
408
- }, [hasSearch]);
409
-
410
408
  if (isSchemaless(schema.name)) {
411
409
  return children;
412
410
  }
@@ -693,11 +691,9 @@ const DatasetColumnItem: React.FC<{
693
691
  const closeAllColumns = useAtomValue(closeAllColumnsAtom);
694
692
  const setExpandedColumns = useSetAtom(expandedColumnsAtom);
695
693
 
696
- React.useEffect(() => {
697
- if (closeAllColumns) {
698
- setIsExpanded(false);
699
- }
700
- }, [closeAllColumns]);
694
+ if (closeAllColumns && isExpanded) {
695
+ setIsExpanded(false);
696
+ }
701
697
 
702
698
  if (isExpanded) {
703
699
  setExpandedColumns(
@@ -13,11 +13,14 @@ import {
13
13
  export class StreamingChunkTransport<
14
14
  UI_MESSAGE extends UIMessage,
15
15
  > extends DefaultChatTransport<UI_MESSAGE> {
16
+ private onChunkReceived: (chunk: UIMessageChunk) => void;
17
+
16
18
  constructor(
17
19
  options: HttpChatTransportInitOptions<UI_MESSAGE> = {},
18
- private onChunkReceived: (chunk: UIMessageChunk) => void,
20
+ onChunkReceived: (chunk: UIMessageChunk) => void,
19
21
  ) {
20
22
  super(options);
23
+ this.onChunkReceived = onChunkReceived;
21
24
  }
22
25
 
23
26
  protected override processResponseStream(