@marimo-team/frontend 0.16.0 → 0.16.2

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 (284) hide show
  1. package/dist/assets/ConnectedDataExplorerComponent-B5cPvWoQ.js +19 -0
  2. package/dist/assets/{ImageComparisonComponent-fTHv1Ih0.js → ImageComparisonComponent-CqR26LSv.js} +1 -1
  3. package/dist/assets/{VegaLite-Bdi-TyfY.js → VegaLite-DvQDATwI.js} +1 -1
  4. package/dist/assets/_baseEach--KDTwKbG.js +1 -0
  5. package/dist/assets/_baseMap-Cu3o-eyO.js +1 -0
  6. package/dist/assets/{_baseUniq-CCgDNtZb.js → _baseUniq-y7ZXnMo1.js} +1 -1
  7. package/dist/assets/{_createAggregator-DcD0kTA5.js → _createAggregator-ZcHkHPNJ.js} +1 -1
  8. package/dist/assets/{agent-panel-Crv430aI.js → agent-panel-B91RoLct.js} +76 -57
  9. package/dist/assets/{any-language-editor-CQh552Wu.js → any-language-editor-CxfHcm5h.js} +1 -1
  10. package/dist/assets/{architectureDiagram-W76B3OCA-BAJeBxzt.js → architectureDiagram-W76B3OCA-BQsvK8uR.js} +1 -1
  11. package/dist/assets/{between-horizontal-start-Boxgxbt_.js → between-horizontal-start-BmYToIaM.js} +1 -1
  12. package/dist/assets/{blockDiagram-QIGZ2CNN-CL-1svEK.js → blockDiagram-QIGZ2CNN-r3HgCj4w.js} +1 -1
  13. package/dist/assets/{c4Diagram-FPNF74CW-BbEqbCTl.js → c4Diagram-FPNF74CW-BJbPNt41.js} +1 -1
  14. package/dist/assets/channel-DFaEx1fu.js +1 -0
  15. package/dist/assets/chat-panel-IoPMv8e2.js +3 -0
  16. package/dist/assets/{chunk-4BX2VUAB-C--8TXeE.js → chunk-4BX2VUAB-Dv4MZ9Hj.js} +1 -1
  17. package/dist/assets/{chunk-55IACEB6-Bj00HDqq.js → chunk-55IACEB6-CM4AHquB.js} +1 -1
  18. package/dist/assets/{chunk-FMBD7UC4-C-lhB6hN.js → chunk-FMBD7UC4-C_Zz0ENB.js} +1 -1
  19. package/dist/assets/{chunk-K7UQS3LO-B-pGTXPt.js → chunk-K7UQS3LO-DYSmiXYq.js} +1 -1
  20. package/dist/assets/{chunk-QN33PNHL-DqUzGhvm.js → chunk-QN33PNHL-QM4OPuQP.js} +1 -1
  21. package/dist/assets/{chunk-QZHKN3VN-TntJHfSk.js → chunk-QZHKN3VN-CfAsGyeB.js} +1 -1
  22. package/dist/assets/{chunk-TVAH2DTR-HUJb1psV.js → chunk-TVAH2DTR-6j_Cpjsi.js} +1 -1
  23. package/dist/assets/{chunk-TZMSLE5B-BK3C__t3.js → chunk-TZMSLE5B-BHslFJQE.js} +1 -1
  24. package/dist/assets/{circle-play-DBLOv1Yu.js → circle-play-CK3UZRYQ.js} +1 -1
  25. package/dist/assets/classDiagram-KNZD7YFC-BsZtvV5O.js +1 -0
  26. package/dist/assets/classDiagram-v2-RKCZMP56-BsZtvV5O.js +1 -0
  27. package/dist/assets/{clear-button-BeoFbEKH.js → clear-button-C4fDVSv8.js} +1 -1
  28. package/dist/assets/clone-YBEvPE-s.js +1 -0
  29. package/dist/assets/command-palette-D7hOfvf6.js +1 -0
  30. package/dist/assets/{common-C7oJcmCT.js → common-D-lbuUwz.js} +1 -1
  31. package/dist/assets/{compile-7L0MwhyI.js → compile-DVQe1Mzk.js} +1 -1
  32. package/dist/assets/{cose-bilkent-S5V4N54A-BMkGLcVC.js → cose-bilkent-S5V4N54A-D-IS7WC8.js} +1 -1
  33. package/dist/assets/{dagre-5GWH7T2D-BJtRienS.js → dagre-5GWH7T2D-lYu-tEWT.js} +1 -1
  34. package/dist/assets/{data-grid-overlay-editor-DBkmGtNs.js → data-grid-overlay-editor-C5peOCit.js} +1 -1
  35. package/dist/assets/datasources-panel-D3NA20uZ.js +1 -0
  36. package/dist/assets/{dependency-graph-panel-DEdOxp2X.js → dependency-graph-panel-BGVYOfkV.js} +1 -1
  37. package/dist/assets/{diagram-N5W7TBWH-CmECY3nb.js → diagram-N5W7TBWH-BnvIuYUp.js} +1 -1
  38. package/dist/assets/{diagram-QEK2KX5R-DMOVSNKD.js → diagram-QEK2KX5R-DemedRK3.js} +1 -1
  39. package/dist/assets/{diagram-S2PKOQOG-BiJ96PNQ.js → diagram-S2PKOQOG-iiY7AuyH.js} +1 -1
  40. package/dist/assets/{documentation-panel-xULhaEv3.js → documentation-panel-C3dSwOSQ.js} +1 -1
  41. package/dist/assets/edit-page-C5TsEeSo.js +129 -0
  42. package/dist/assets/{ellipsis-vertical-BBqXIlc2.js → ellipsis-vertical-CazJl8M7.js} +1 -1
  43. package/dist/assets/{empty-state-B3dA3G5P.js → empty-state-DW308mFO.js} +1 -1
  44. package/dist/assets/{erDiagram-AWTI2OKA-MP1DiFRo.js → erDiagram-AWTI2OKA-6wQ8Ugg0.js} +1 -1
  45. package/dist/assets/{error-panel-Cc1sv-Ag.js → error-panel-D1VnJ1yP.js} +1 -1
  46. package/dist/assets/file-explorer-panel-0oVd4t-D.js +1 -0
  47. package/dist/assets/{flowDiagram-PVAE7QVJ-BX7caPp7.js → flowDiagram-PVAE7QVJ-C55IUWjm.js} +1 -1
  48. package/dist/assets/{ganttDiagram-OWAHRB6G-B462g4Yf.js → ganttDiagram-OWAHRB6G-DmqCM6ME.js} +4 -4
  49. package/dist/assets/{gitGraphDiagram-NY62KEGX-CGgvZ9-9.js → gitGraphDiagram-NY62KEGX-DBvhAeM_.js} +1 -1
  50. package/dist/assets/{glide-data-editor-C0gUFZON.js → glide-data-editor-CHNuHidQ.js} +4 -4
  51. package/dist/assets/{graph-CHRVBzY5.js → graph-CG6BgUWQ.js} +1 -1
  52. package/dist/assets/home-page-dgivXuSR.js +9 -0
  53. package/dist/assets/{index-Clbi_Yaq.js → index-BTGpssVX.js} +1 -1
  54. package/dist/assets/{index-CQDrxQ0j.js → index-BYVZlBF8.js} +1 -1
  55. package/dist/assets/{index-lYa_leQE.js → index-BelfnXwL.js} +1 -1
  56. package/dist/assets/{index-DRMm6SNo.js → index-BneyUujp.js} +1 -1
  57. package/dist/assets/{index-BY93Ejhl.js → index-C02SqeRj.js} +1 -1
  58. package/dist/assets/{index-C-8WADat.js → index-C7dtgr9A.js} +1 -1
  59. package/dist/assets/{index-D9UKkrr2.js → index-CAQvMTzM.js} +1 -1
  60. package/dist/assets/index-CGDMlQfO.css +1 -0
  61. package/dist/assets/index-CelXfcd8.js +580 -0
  62. package/dist/assets/{index-vmICa5KN.js → index-Csd6QrCV.js} +1 -1
  63. package/dist/assets/{index-DoRmcrKM.js → index-CtPksxf0.js} +1 -1
  64. package/dist/assets/{index-C1v_Z9et.js → index-Cxyk7pt-.js} +1 -1
  65. package/dist/assets/{index-CpTPJo4k.js → index-DAZ-9ri2.js} +1 -1
  66. package/dist/assets/{index-DEQvTChO.js → index-DONRrmA2.js} +1 -1
  67. package/dist/assets/{index-C4Tn5NvJ.js → index-Db36XTG_.js} +1 -1
  68. package/dist/assets/{index-z9bohSQJ.js → index-DdIhdEVw.js} +1 -1
  69. package/dist/assets/{index-C-GhZ7ti.js → index-M_pBKDSe.js} +1 -1
  70. package/dist/assets/{index-C77h_TXN.js → index-_luCZMLM.js} +1 -1
  71. package/dist/assets/{index-D1vmG6DS.js → index-mkubqy9-.js} +1 -1
  72. package/dist/assets/{index-BVgAenPd.js → index-sbO9UaUU.js} +1 -1
  73. package/dist/assets/{index-CWMgowgL.js → index-z4krxQ4j.js} +1 -1
  74. package/dist/assets/infoDiagram-STP46IZ2-wTALjfPc.js +2 -0
  75. package/dist/assets/{isEmpty-DU_ogP_D.js → isEmpty-CqX_YTIf.js} +1 -1
  76. package/dist/assets/{journeyDiagram-BIP6EPQ6-C6EgLP_Q.js → journeyDiagram-BIP6EPQ6-Y5w_Tqe_.js} +1 -1
  77. package/dist/assets/{kanban-definition-6OIFK2YF-BXzYO1yj.js → kanban-definition-6OIFK2YF-DbXs5Rxi.js} +1 -1
  78. package/dist/assets/{layout-jihVw5-i.js → layout-BCNPDACj.js} +1 -1
  79. package/dist/assets/{linear-C4blANlC.js → linear-uO6UVhXt.js} +1 -1
  80. package/dist/assets/links-Drv7cJgN.js +7 -0
  81. package/dist/assets/{logs-panel-D401qzZh.js → logs-panel-BEQ1eRUp.js} +1 -1
  82. package/dist/assets/{markdown-renderer-Cd9eYyaL.js → markdown-renderer-Dmzbb00W.js} +20 -20
  83. package/dist/assets/{mermaid-BEVuRz_O.js → mermaid-qRc4MXIj.js} +1 -1
  84. package/dist/assets/{mermaid.core-CaSnaLH0.js → mermaid.core-CvvJtCRj.js} +4 -4
  85. package/dist/assets/min-DYUOb1RR.js +1 -0
  86. package/dist/assets/{mindmap-definition-Q6HEUPPD-BXUM5MT2.js → mindmap-definition-Q6HEUPPD-G5NognM-.js} +1 -1
  87. package/dist/assets/{number-overlay-editor-4uWXGlPG.js → number-overlay-editor-DPr5sHFu.js} +1 -1
  88. package/dist/assets/outline-panel-gxQXvVi4.js +1 -0
  89. package/dist/assets/{packages-panel-CJL0MVlj.js → packages-panel-B1T0VPlg.js} +1 -1
  90. package/dist/assets/{pieDiagram-ADFJNKIX-Dxt5PVNo.js → pieDiagram-ADFJNKIX-DK9SHkfc.js} +1 -1
  91. package/dist/assets/{quadrantDiagram-LMRXKWRM-D4pUaA31.js → quadrantDiagram-LMRXKWRM-D1DdWF8C.js} +1 -1
  92. package/dist/assets/{react-plotly-cJZ0VWBq.js → react-plotly-CTwajqCb.js} +1 -1
  93. package/dist/assets/{requirementDiagram-4UW4RH46-DVRTjgas.js → requirementDiagram-4UW4RH46-DnjDAypr.js} +1 -1
  94. package/dist/assets/{run-page-BUEnMC9w.js → run-page-CQY9im22.js} +1 -1
  95. package/dist/assets/{sankeyDiagram-GR3RE2ED-CVFnD9C-.js → sankeyDiagram-GR3RE2ED-B67Va-ER.js} +1 -1
  96. package/dist/assets/{scratchpad-panel-BIgRENkI.js → scratchpad-panel-DlDfcDtW.js} +1 -1
  97. package/dist/assets/{secrets-panel-xY5-V_BD.js → secrets-panel-BDGyuGZA.js} +1 -1
  98. package/dist/assets/{sequenceDiagram-C3RYC4MD-_lY4ZN_S.js → sequenceDiagram-C3RYC4MD-DiWgZPtN.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-DhpPRtQp.js} +1 -1
  101. package/dist/assets/snippets-panel-CLkBXhJ2.js +1 -0
  102. package/dist/assets/sortBy-D4OG7w4O.js +1 -0
  103. package/dist/assets/{state-C4NiC9tO.js → state-Dz_3JyED.js} +1 -1
  104. package/dist/assets/{stateDiagram-KXAO66HF-Da0JQWCn.js → stateDiagram-KXAO66HF-ByF2AULw.js} +1 -1
  105. package/dist/assets/stateDiagram-v2-UMBNRL4Z-CtBJqosP.js +1 -0
  106. package/dist/assets/storage-Dr0CC44z.js +26 -0
  107. package/dist/assets/{terminal-BPwTkXae.js → terminal-BtdissBf.js} +1 -1
  108. package/dist/assets/{time-Dv5_Ouz_.js → time-DKdOTnQg.js} +1 -1
  109. package/dist/assets/{timeline-definition-XQNQX7LJ-Dxh5Zu2e.js → timeline-definition-XQNQX7LJ-DzER9bf6.js} +1 -1
  110. package/dist/assets/tracing-Dpx5M-u3.js +2 -0
  111. package/dist/assets/{tracing-panel-DAzrzNmm.js → tracing-panel-hCjBkSER.js} +2 -2
  112. package/dist/assets/{trash-Dc6DSjz_.js → trash-C6Ko-g5q.js} +1 -1
  113. package/dist/assets/{tree-jheoerAX.js → tree-BHN2gcCF.js} +6 -6
  114. package/dist/assets/{treemap-75Q7IDZK-IgpxeGaf.js → treemap-75Q7IDZK-DR79Mhzt.js} +27 -27
  115. package/dist/assets/variable-panel-PFBCFz36.js +1 -0
  116. package/dist/assets/{vega-component-BpfpiPKI.js → vega-component-Db6-uY4C.js} +1 -1
  117. package/dist/assets/worker-fHbtoWvT.js +1 -0
  118. package/dist/assets/{xychartDiagram-6GGTOJPD-CmNigJ31.js → xychartDiagram-6GGTOJPD-DWzBP3tZ.js} +1 -1
  119. package/dist/index.html +2 -2
  120. package/package.json +5 -4
  121. package/src/__mocks__/requests.ts +1 -0
  122. package/src/components/app-config/user-config-form.tsx +46 -1
  123. package/src/components/chat/acp/__tests__/__snapshots__/prompt.test.ts.snap +62 -43
  124. package/src/components/chat/acp/__tests__/atoms.test.ts +1 -1
  125. package/src/components/chat/acp/__tests__/state.test.ts +36 -36
  126. package/src/components/chat/acp/agent-panel.tsx +24 -27
  127. package/src/components/chat/acp/blocks.tsx +6 -6
  128. package/src/components/chat/acp/prompt.ts +62 -43
  129. package/src/components/chat/chat-panel.tsx +5 -1
  130. package/src/components/chat/markdown-renderer.tsx +6 -10
  131. package/src/components/chat/tool-call-accordion.tsx +52 -20
  132. package/src/components/data-table/SearchBar.tsx +8 -7
  133. package/src/components/data-table/__tests__/column_formatting.test.ts +50 -35
  134. package/src/components/data-table/__tests__/columns.test.tsx +38 -0
  135. package/src/components/data-table/__tests__/data-table.test.tsx +39 -1
  136. package/src/components/data-table/cell-hover-template/feature.ts +14 -0
  137. package/src/components/data-table/cell-hover-template/types.ts +11 -0
  138. package/src/components/data-table/charts/components/form-fields.tsx +41 -37
  139. package/src/components/data-table/charts/forms/common-chart.tsx +2 -2
  140. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +5 -2
  141. package/src/components/data-table/column-formatting/feature.ts +62 -29
  142. package/src/components/data-table/column-formatting/types.ts +1 -0
  143. package/src/components/data-table/column-header.tsx +3 -1
  144. package/src/components/data-table/column-summary/chart-spec-model.tsx +24 -7
  145. package/src/components/data-table/column-summary/column-summary.tsx +18 -9
  146. package/src/components/data-table/columns.tsx +63 -20
  147. package/src/components/data-table/data-table.tsx +10 -2
  148. package/src/components/data-table/date-popover.tsx +85 -75
  149. package/src/components/data-table/filter-pills.tsx +14 -9
  150. package/src/components/data-table/header-items.tsx +5 -1
  151. package/src/components/data-table/pagination.tsx +20 -13
  152. package/src/components/data-table/renderers.tsx +36 -0
  153. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +10 -8
  154. package/src/components/data-table/schemas.ts +16 -0
  155. package/src/components/datasources/column-preview.tsx +6 -2
  156. package/src/components/datasources/datasources.tsx +8 -12
  157. package/src/components/editor/Cell.tsx +2 -0
  158. package/src/components/editor/ai/transport/chat-transport.tsx +4 -1
  159. package/src/components/editor/cell/CellStatus.tsx +23 -20
  160. package/src/components/editor/cell/CreateCellButton.tsx +3 -4
  161. package/src/components/editor/cell/code/language-toggle.tsx +3 -4
  162. package/src/components/editor/chrome/wrapper/footer-items/machine-stats.tsx +39 -28
  163. package/src/components/editor/controls/notebook-menu-dropdown.tsx +4 -2
  164. package/src/components/editor/errors/sql-validation-errors.tsx +34 -0
  165. package/src/components/editor/file-tree/requesting-tree.tsx +14 -8
  166. package/src/components/editor/output/ConsoleOutput.tsx +13 -1
  167. package/src/components/editor/output/MarimoErrorOutput.tsx +60 -1
  168. package/src/components/editor/renderers/CellArray.tsx +3 -4
  169. package/src/components/editor/renderers/slides-layout/slides-layout.tsx +3 -3
  170. package/src/components/editor/renderers/slides-layout/types.ts +1 -0
  171. package/src/components/pages/home-page.tsx +4 -1
  172. package/src/components/slides/slides-component.tsx +1 -1
  173. package/src/components/slides/slides.css +6 -0
  174. package/src/components/terminal/theme.tsx +1 -0
  175. package/src/components/tracing/tracing-spec.ts +5 -4
  176. package/src/components/ui/range-slider.tsx +4 -2
  177. package/src/components/ui/slider.tsx +3 -1
  178. package/src/components/variables/variables-table.tsx +3 -0
  179. package/src/core/MarimoApp.tsx +9 -6
  180. package/src/core/ai/context/__tests__/registry.test.ts +6 -4
  181. package/src/core/ai/context/providers/cell-output.ts +4 -20
  182. package/src/core/ai/context/providers/error.ts +3 -1
  183. package/src/core/ai/context/providers/file.ts +7 -2
  184. package/src/core/ai/context/providers/tables.ts +3 -2
  185. package/src/core/ai/context/providers/variable.ts +6 -4
  186. package/src/core/ai/staged-cells.ts +34 -1
  187. package/src/core/cells/__tests__/add-missing-import.test.ts +67 -22
  188. package/src/core/cells/add-missing-import.ts +24 -7
  189. package/src/core/cells/cells.ts +26 -27
  190. package/src/core/cells/logs.ts +1 -1
  191. package/src/core/codemirror/find-replace/search-highlight.ts +3 -1
  192. package/src/core/codemirror/language/LanguageAdapters.ts +9 -3
  193. package/src/core/codemirror/language/__tests__/extension.test.ts +24 -0
  194. package/src/core/codemirror/language/__tests__/sql-validation.test.ts +133 -0
  195. package/src/core/codemirror/language/languages/sql/sql-mode.ts +20 -0
  196. package/src/core/codemirror/language/languages/sql/sql.ts +90 -3
  197. package/src/core/codemirror/language/languages/sql/validation-errors.ts +79 -0
  198. package/src/core/codemirror/language/panel/panel.tsx +8 -2
  199. package/src/core/codemirror/language/panel/sql.tsx +81 -4
  200. package/src/core/codemirror/lsp/notebook-lsp.ts +8 -2
  201. package/src/core/codemirror/readonly/__tests__/extension.test.ts +1 -1
  202. package/src/core/codemirror/rtc/loro/awareness.ts +52 -17
  203. package/src/core/codemirror/rtc/loro/sync.ts +12 -4
  204. package/src/core/config/config-schema.ts +1 -0
  205. package/src/core/config/config.ts +4 -0
  206. package/src/core/config/feature-flag.tsx +3 -1
  207. package/src/core/datasets/request-registry.ts +17 -1
  208. package/src/core/hotkeys/hotkeys.ts +8 -4
  209. package/src/core/i18n/__tests__/locale-provider.test.tsx +176 -0
  210. package/src/core/i18n/locale-provider.tsx +35 -0
  211. package/src/core/i18n/with-locale.tsx +12 -0
  212. package/src/core/islands/bridge.ts +1 -0
  213. package/src/core/islands/components/web-components.tsx +13 -10
  214. package/src/core/islands/main.ts +1 -0
  215. package/src/core/kernel/RuntimeState.ts +4 -1
  216. package/src/core/kernel/messages.ts +3 -2
  217. package/src/core/network/DeferredRequestRegistry.ts +16 -4
  218. package/src/core/network/requests-network.ts +7 -0
  219. package/src/core/network/requests-static.ts +1 -0
  220. package/src/core/network/requests-toasting.ts +1 -0
  221. package/src/core/network/types.ts +2 -0
  222. package/src/core/runtime/runtime.ts +5 -4
  223. package/src/core/wasm/bridge.ts +10 -1
  224. package/src/core/wasm/store.ts +4 -1
  225. package/src/core/wasm/worker/message-buffer.ts +3 -2
  226. package/src/core/websocket/types.ts +22 -16
  227. package/src/core/websocket/useMarimoWebSocket.tsx +4 -0
  228. package/src/hooks/useFormatting.ts +97 -0
  229. package/src/hooks/useTimer.ts +8 -5
  230. package/src/plugins/core/registerReactComponent.tsx +39 -29
  231. package/src/plugins/impl/DataTablePlugin.tsx +15 -4
  232. package/src/plugins/impl/RangeSliderPlugin.tsx +5 -3
  233. package/src/plugins/impl/SliderPlugin.tsx +3 -1
  234. package/src/plugins/impl/anywidget/model.ts +16 -5
  235. package/src/plugins/impl/data-editor/types.ts +7 -5
  236. package/src/plugins/impl/data-explorer/components/column-summary.tsx +20 -13
  237. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +17 -5
  238. package/src/plugins/impl/panel/utils.ts +6 -4
  239. package/src/plugins/layout/OutlinePlugin.tsx +69 -0
  240. package/src/plugins/layout/StatPlugin.tsx +4 -1
  241. package/src/plugins/plugins.ts +2 -0
  242. package/src/stories/dataframe.stories.tsx +2 -0
  243. package/src/utils/__tests__/dates.test.ts +45 -24
  244. package/src/utils/__tests__/dom.test.ts +167 -0
  245. package/src/utils/__tests__/numbers.test.ts +42 -30
  246. package/src/utils/__tests__/once.test.ts +187 -0
  247. package/src/utils/dates.ts +15 -10
  248. package/src/utils/dom.ts +55 -0
  249. package/src/utils/edit-distance.ts +8 -6
  250. package/src/utils/errors.ts +1 -1
  251. package/src/utils/id-tree.tsx +21 -10
  252. package/src/utils/localStorage.ts +13 -4
  253. package/src/utils/numbers.ts +11 -11
  254. package/src/utils/once.ts +32 -0
  255. package/src/utils/paths.ts +4 -1
  256. package/src/utils/pluralize.ts +12 -5
  257. package/src/utils/python-poet/poet.ts +30 -15
  258. package/src/utils/time.ts +5 -1
  259. package/dist/assets/ConnectedDataExplorerComponent-BErMbWvG.js +0 -19
  260. package/dist/assets/_baseEach-CNBxBxvS.js +0 -1
  261. package/dist/assets/_baseMap-D1WHjKrd.js +0 -1
  262. package/dist/assets/channel-_2eNSz0n.js +0 -1
  263. package/dist/assets/chat-panel-CXh5Wl6C.js +0 -3
  264. package/dist/assets/classDiagram-KNZD7YFC-BGmh9POF.js +0 -1
  265. package/dist/assets/classDiagram-v2-RKCZMP56-BGmh9POF.js +0 -1
  266. package/dist/assets/clone-BFDSPAj3.js +0 -1
  267. package/dist/assets/command-palette-CXZiSv0I.js +0 -1
  268. package/dist/assets/datasources-panel-B7FbYLiy.js +0 -1
  269. package/dist/assets/edit-page-BrYda9VE.js +0 -129
  270. package/dist/assets/file-explorer-panel-Bw59Kva1.js +0 -1
  271. package/dist/assets/home-page-Fb2osjys.js +0 -9
  272. package/dist/assets/index-Cx0bsY1w.css +0 -1
  273. package/dist/assets/index-DKEudB02.js +0 -578
  274. package/dist/assets/infoDiagram-STP46IZ2-CVyrdLc8.js +0 -2
  275. package/dist/assets/links-D59GIweI.js +0 -7
  276. package/dist/assets/min-DUMu_zeK.js +0 -1
  277. package/dist/assets/outline-panel-DIzkvm2I.js +0 -1
  278. package/dist/assets/snippets-panel-CTPYW41n.js +0 -1
  279. package/dist/assets/sortBy-BNZKwiq_.js +0 -1
  280. package/dist/assets/stateDiagram-v2-UMBNRL4Z-D5lYZOOt.js +0 -1
  281. package/dist/assets/storage-CMdLzB_c.js +0 -26
  282. package/dist/assets/tracing-BCIurUfa.js +0 -2
  283. package/dist/assets/variable-panel-DYAiLBmF.js +0 -1
  284. package/dist/assets/worker-X5rxzQGQ.js +0 -1
@@ -0,0 +1,79 @@
1
+ /* Copyright 2024 Marimo. All rights reserved. */
2
+
3
+ import type { SupportedDialects } from "@marimo-team/codemirror-sql";
4
+ import { atom, useAtomValue } from "jotai";
5
+ import type { CellId } from "@/core/cells/ids";
6
+ import { store } from "@/core/state/jotai";
7
+
8
+ export interface SQLValidationError {
9
+ errorType: string;
10
+ errorMessage: string;
11
+ codeblock?: string; // Code block that caused the error
12
+ }
13
+
14
+ type CellToSQLErrors = Map<CellId, SQLValidationError>;
15
+
16
+ export const sqlValidationErrorsAtom = atom<CellToSQLErrors>(
17
+ new Map<CellId, SQLValidationError>(),
18
+ );
19
+
20
+ export const useSqlValidationErrorsForCell = (cellId: CellId) => {
21
+ const sqlValidationErrors = useAtomValue(sqlValidationErrorsAtom);
22
+ return sqlValidationErrors.get(cellId);
23
+ };
24
+
25
+ export function clearSqlValidationError(cellId: CellId) {
26
+ const sqlValidationErrors = store.get(sqlValidationErrorsAtom);
27
+ const newErrors = new Map(sqlValidationErrors);
28
+ newErrors.delete(cellId);
29
+ store.set(sqlValidationErrorsAtom, newErrors);
30
+ }
31
+
32
+ export function setSqlValidationError({
33
+ cellId,
34
+ error,
35
+ dialect,
36
+ }: {
37
+ cellId: CellId;
38
+ error: string;
39
+ dialect: SupportedDialects | null;
40
+ }) {
41
+ const sqlValidationErrors = store.get(sqlValidationErrorsAtom);
42
+ const newErrors = new Map(sqlValidationErrors);
43
+
44
+ const errorResult: SQLValidationError =
45
+ dialect === "DuckDB" ? handleDuckdbError(error) : splitErrorMessage(error);
46
+
47
+ newErrors.set(cellId, errorResult);
48
+ store.set(sqlValidationErrorsAtom, newErrors);
49
+ }
50
+
51
+ function handleDuckdbError(error: string): SQLValidationError {
52
+ const { errorType, errorMessage } = splitErrorMessage(error);
53
+ let newErrorMessage = errorMessage;
54
+
55
+ // Extract the LINE and the rest of the message as codeblock, keep errorMessage as whatever is before
56
+ let codeblock: string | undefined;
57
+ const lineIndex = errorMessage.indexOf("LINE ");
58
+ if (lineIndex !== -1) {
59
+ codeblock = errorMessage.slice(Math.max(0, lineIndex)).trim();
60
+ newErrorMessage = errorMessage.slice(0, Math.max(0, lineIndex)).trim();
61
+ }
62
+
63
+ return {
64
+ errorType,
65
+ errorMessage: newErrorMessage,
66
+ codeblock,
67
+ };
68
+ }
69
+
70
+ function splitErrorMessage(error: string) {
71
+ const errorType = error.split(":")[0].trim();
72
+ const errorMessage = error.split(":").slice(1).join(":").trim();
73
+ return { errorType, errorMessage };
74
+ }
75
+
76
+ export const exportedForTesting = {
77
+ splitErrorMessage,
78
+ handleDuckdbError,
79
+ };
@@ -5,7 +5,8 @@ import { Button } from "@/components/ui/button";
5
5
  import { Checkbox } from "@/components/ui/checkbox";
6
6
  import { Tooltip, TooltipProvider } from "@/components/ui/tooltip";
7
7
  import { normalizeName } from "@/core/cells/names";
8
- import type { ConnectionName } from "@/core/datasets/engines";
8
+ import { getFeatureFlag } from "@/core/config/feature-flag";
9
+ import { type ConnectionName, DUCKDB_ENGINE } from "@/core/datasets/engines";
9
10
  import { useAutoGrowInputProps } from "@/hooks/useAutoGrowInputProps";
10
11
  import { formatSQL } from "../../format";
11
12
  import { languageAdapterState } from "../extension";
@@ -22,7 +23,7 @@ import {
22
23
  import type { LanguageMetadataOf } from "../types";
23
24
  import type { QuotePrefixKind } from "../utils/quotes";
24
25
  import { getQuotePrefix, MarkdownQuotePrefixTooltip } from "./markdown";
25
- import { SQLEngineSelect } from "./sql";
26
+ import { SQLEngineSelect, SQLModeSelect } from "./sql";
26
27
 
27
28
  const Divider = () => <div className="h-4 border-r border-border" />;
28
29
 
@@ -70,6 +71,8 @@ export const LanguagePanelComponent: React.FC<{
70
71
  updateSQLDialectFromConnection(view, engine);
71
72
  };
72
73
 
74
+ const sqlModeEnabled = getFeatureFlag("sql_mode");
75
+
73
76
  actions = (
74
77
  <div className="flex flex-1 gap-2 items-center">
75
78
  <label className="flex gap-2 items-center">
@@ -95,6 +98,9 @@ export const LanguagePanelComponent: React.FC<{
95
98
  onChange={switchEngine}
96
99
  />
97
100
  <div className="flex items-center gap-2 ml-auto">
101
+ {sqlModeEnabled && metadata.engine === DUCKDB_ENGINE && (
102
+ <SQLModeSelect />
103
+ )}
98
104
  <Tooltip content="Format SQL">
99
105
  <Button
100
106
  variant="text"
@@ -1,9 +1,16 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
2
 
3
+ import type { SelectTriggerProps } from "@radix-ui/react-select";
3
4
  import { useAtomValue } from "jotai";
4
- import { AlertCircle, CircleHelpIcon } from "lucide-react";
5
+ import {
6
+ AlertCircle,
7
+ CircleHelpIcon,
8
+ DatabaseBackup,
9
+ SearchCheck,
10
+ } from "lucide-react";
5
11
  import { transformDisplayName } from "@/components/databases/display";
6
12
  import { DatabaseLogo } from "@/components/databases/icon";
13
+ import { Button } from "@/components/ui/button";
7
14
  import {
8
15
  Select,
9
16
  SelectContent,
@@ -14,6 +21,7 @@ import {
14
21
  SelectTrigger,
15
22
  SelectValue,
16
23
  } from "@/components/ui/select";
24
+ import { Tooltip } from "@/components/ui/tooltip";
17
25
  import {
18
26
  dataConnectionsMapAtom,
19
27
  setLatestEngineSelected,
@@ -24,6 +32,7 @@ import {
24
32
  } from "@/core/datasets/engines";
25
33
  import type { DataSourceConnection } from "@/core/kernel/messages";
26
34
  import { useNonce } from "@/hooks/useNonce";
35
+ import { type SQLMode, useSQLMode } from "../languages/sql/sql-mode";
27
36
 
28
37
  interface SelectProps {
29
38
  selectedEngine: ConnectionName;
@@ -77,7 +86,7 @@ export const SQLEngineSelect: React.FC<SelectProps> = ({
77
86
  <SelectItem key={connection.name} value={connection.name}>
78
87
  <div className="flex items-center gap-1">
79
88
  <DatabaseLogo className="h-3 w-3" name={connection.dialect} />
80
- <span className="truncate">
89
+ <span className="truncate ml-0.5">
81
90
  {transformDisplayName(connection.display_name)}
82
91
  </span>
83
92
  </div>
@@ -88,9 +97,9 @@ export const SQLEngineSelect: React.FC<SelectProps> = ({
88
97
  return (
89
98
  <div className="flex flex-row gap-1 items-center">
90
99
  <Select value={selectedEngine} onValueChange={handleSelectEngine}>
91
- <SelectTrigger className="text-xs border-border shadow-none! ring-0! h-4.5 px-1.5">
100
+ <SQLSelectTrigger>
92
101
  <SelectValue placeholder="Select an engine" />
93
- </SelectTrigger>
102
+ </SQLSelectTrigger>
94
103
  <SelectContent>
95
104
  <SelectGroup>
96
105
  <SelectLabel>Database connections</SelectLabel>
@@ -130,3 +139,71 @@ export const SQLEngineSelect: React.FC<SelectProps> = ({
130
139
  const HELP_KEY = "__help__";
131
140
  const HELP_URL =
132
141
  "http://docs.marimo.io/guides/working_with_data/sql/#connecting-to-a-custom-database";
142
+
143
+ export const SQLModeSelect: React.FC = () => {
144
+ const { sqlMode, setSQLMode } = useSQLMode();
145
+
146
+ const handleToggleMode = () => {
147
+ setSQLMode(sqlMode === "validate" ? "default" : "validate");
148
+ };
149
+
150
+ const getModeIcon = (mode: SQLMode) => {
151
+ return mode === "validate" ? (
152
+ <SearchCheck className="h-3 w-3" />
153
+ ) : (
154
+ <DatabaseBackup className="h-3 w-3" />
155
+ );
156
+ };
157
+
158
+ const getTooltipContent = (mode: SQLMode) => {
159
+ return mode === "validate" ? (
160
+ <div className="text-xs">
161
+ <div className="font-semibold mb-1 flex flex-row items-center gap-1">
162
+ <SearchCheck className="h-3 w-3" />
163
+ Validate Mode
164
+ </div>
165
+ <p>Queries are validated as you write them</p>
166
+ </div>
167
+ ) : (
168
+ <div className="text-xs">
169
+ <div className="font-semibold mb-1 flex flex-row items-center gap-1">
170
+ <DatabaseBackup className="h-3 w-3" />
171
+ Default Mode
172
+ </div>
173
+ <p>Standard editing</p>
174
+ </div>
175
+ );
176
+ };
177
+
178
+ return (
179
+ <div className="flex flex-row gap-1 items-center">
180
+ <Tooltip delayDuration={300} content={getTooltipContent(sqlMode)}>
181
+ <Button
182
+ variant="ghost"
183
+ size="sm"
184
+ onClick={handleToggleMode}
185
+ className="h-5 px-1.5 text-xs border-border shadow-none hover:bg-accent"
186
+ >
187
+ {getModeIcon(sqlMode)}
188
+ <span className="ml-1">
189
+ {sqlMode === "validate" ? "Validate" : "Default"}
190
+ </span>
191
+ </Button>
192
+ </Tooltip>
193
+ </div>
194
+ );
195
+ };
196
+
197
+ const SQLSelectTrigger: React.FC<SelectTriggerProps> = ({
198
+ children,
199
+ ...props
200
+ }) => {
201
+ return (
202
+ <SelectTrigger
203
+ className="text-xs border-border shadow-none! ring-0! h-5 px-1.5 hover:bg-accent transition-colors"
204
+ {...props}
205
+ >
206
+ {children}
207
+ </SelectTrigger>
208
+ );
209
+ };
@@ -18,13 +18,19 @@ import { getLSPDocument } from "./utils";
18
18
 
19
19
  class Snapshotter {
20
20
  private documentVersion = 0;
21
+ private readonly getNotebookCode: () => {
22
+ cellIds: CellId[];
23
+ codes: Record<CellId, string>;
24
+ };
21
25
 
22
26
  constructor(
23
- private readonly getNotebookCode: () => {
27
+ getNotebookCode: () => {
24
28
  cellIds: CellId[];
25
29
  codes: Record<CellId, string>;
26
30
  },
27
- ) {}
31
+ ) {
32
+ this.getNotebookCode = getNotebookCode;
33
+ }
28
34
 
29
35
  /**
30
36
  * Map from the global document version to the cell id and version.
@@ -9,7 +9,7 @@ import { connectionAtom } from "../../../network/connection";
9
9
  import { dynamicReadonly, isEditorReadonly } from "../extension";
10
10
 
11
11
  function makeStoreWithConnection(
12
- state: WebSocketState.CONNECTING | WebSocketState.OPEN,
12
+ state: typeof WebSocketState.CONNECTING | typeof WebSocketState.OPEN,
13
13
  ) {
14
14
  const store = createStore();
15
15
  store.set(connectionAtom, { state });
@@ -202,13 +202,25 @@ export const createSelectionLayer = (): Extension =>
202
202
  * Renders a blinking cursor to indicate the cursor of another user.
203
203
  */
204
204
  export class RemoteCursorMarker implements LayerMarker {
205
+ private left: number;
206
+ private top: number;
207
+ private height: number;
208
+ private name: string;
209
+ private colorClassName: string;
210
+
205
211
  constructor(
206
- private left: number,
207
- private top: number,
208
- private height: number,
209
- private name: string,
210
- private colorClassName: string,
211
- ) {}
212
+ left: number,
213
+ top: number,
214
+ height: number,
215
+ name: string,
216
+ colorClassName: string,
217
+ ) {
218
+ this.left = left;
219
+ this.top = top;
220
+ this.height = height;
221
+ this.name = name;
222
+ this.colorClassName = colorClassName;
223
+ }
212
224
 
213
225
  draw(): HTMLElement {
214
226
  const elt = document.createElement("div");
@@ -353,16 +365,30 @@ export interface CursorPosition {
353
365
 
354
366
  export class AwarenessPlugin implements PluginValue {
355
367
  sub: Subscription;
368
+ public view: EditorView;
369
+ public doc: LoroDoc;
370
+ public user: UserState;
371
+ public awareness: Awareness<AwarenessState>;
372
+ private getTextFromDoc: (doc: LoroDoc) => LoroText;
373
+ private scopeId: ScopeId;
374
+ private getUserId?: () => Uid;
356
375
 
357
376
  constructor(
358
- public view: EditorView,
359
- public doc: LoroDoc,
360
- public user: UserState,
361
- public awareness: Awareness<AwarenessState>,
362
- private getTextFromDoc: (doc: LoroDoc) => LoroText,
363
- private scopeId: ScopeId,
364
- private getUserId?: () => Uid,
377
+ view: EditorView,
378
+ doc: LoroDoc,
379
+ user: UserState,
380
+ awareness: Awareness<AwarenessState>,
381
+ getTextFromDoc: (doc: LoroDoc) => LoroText,
382
+ scopeId: ScopeId,
383
+ getUserId?: () => Uid,
365
384
  ) {
385
+ this.view = view;
386
+ this.doc = doc;
387
+ this.user = user;
388
+ this.awareness = awareness;
389
+ this.getTextFromDoc = getTextFromDoc;
390
+ this.scopeId = scopeId;
391
+ this.getUserId = getUserId;
366
392
  this.sub = this.doc.subscribe((e) => {
367
393
  if (e.by === "local") {
368
394
  // update remote cursor position
@@ -435,12 +461,21 @@ export class AwarenessPlugin implements PluginValue {
435
461
  }
436
462
  export class RemoteAwarenessPlugin implements PluginValue {
437
463
  _awarenessListener?: AwarenessListener;
464
+ public view: EditorView;
465
+ public doc: LoroDoc;
466
+ public awareness: Awareness<AwarenessState>;
467
+ private scopeId: ScopeId;
468
+
438
469
  constructor(
439
- public view: EditorView,
440
- public doc: LoroDoc,
441
- public awareness: Awareness<AwarenessState>,
442
- private scopeId: ScopeId,
470
+ view: EditorView,
471
+ doc: LoroDoc,
472
+ awareness: Awareness<AwarenessState>,
473
+ scopeId: ScopeId,
443
474
  ) {
475
+ this.view = view;
476
+ this.doc = doc;
477
+ this.awareness = awareness;
478
+ this.scopeId = scopeId;
444
479
  const listener: AwarenessListener = async (arg, origin) => {
445
480
  if (origin === "local") {
446
481
  return;
@@ -40,13 +40,21 @@ export const loroSyncAnnotation = Annotation.define();
40
40
  export class LoroSyncPluginValue implements PluginValue {
41
41
  sub?: Subscription;
42
42
  private isInitDispatch = false;
43
+ private view: EditorView;
44
+ private doc: LoroDoc;
45
+ private docPath: string[];
46
+ private getTextFromDoc: (doc: LoroDoc) => LoroText;
43
47
 
44
48
  constructor(
45
- private view: EditorView,
46
- private doc: LoroDoc,
47
- private docPath: string[],
48
- private getTextFromDoc: (doc: LoroDoc) => LoroText,
49
+ view: EditorView,
50
+ doc: LoroDoc,
51
+ docPath: string[],
52
+ getTextFromDoc: (doc: LoroDoc) => LoroText,
49
53
  ) {
54
+ this.view = view;
55
+ this.doc = doc;
56
+ this.docPath = docPath;
57
+ this.getTextFromDoc = getTextFromDoc;
50
58
  this.sub = doc.subscribe(this.onRemoteUpdate);
51
59
  Promise.resolve().then(() => {
52
60
  this.isInitDispatch = true;
@@ -144,6 +144,7 @@ export const UserConfigSchema = z
144
144
  }
145
145
  return width;
146
146
  }),
147
+ locale: z.string().nullable().optional(),
147
148
  reference_highlighting: z.boolean().default(false),
148
149
  })
149
150
  .passthrough()
@@ -82,6 +82,10 @@ export const editorFontSizeAtom = atom<number>((get) => {
82
82
  return get(resolvedMarimoConfigAtom).display.code_editor_font_size;
83
83
  });
84
84
 
85
+ export const localeAtom = atom<string | null | undefined>((get) => {
86
+ return get(resolvedMarimoConfigAtom).display.locale;
87
+ });
88
+
85
89
  export function isAiEnabled(config: UserConfig) {
86
90
  return (
87
91
  Boolean(config.ai?.models?.chat_model) ||
@@ -14,6 +14,7 @@ export interface ExperimentalFeatures {
14
14
  mcp_docs: boolean;
15
15
  sql_linter: boolean;
16
16
  external_agents: boolean;
17
+ sql_mode: boolean;
17
18
  // Add new feature flags here
18
19
  }
19
20
 
@@ -26,13 +27,14 @@ const defaultValues: ExperimentalFeatures = {
26
27
  mcp_docs: false,
27
28
  sql_linter: false,
28
29
  external_agents: import.meta.env.DEV,
30
+ sql_mode: false,
29
31
  };
30
32
 
31
33
  export function getFeatureFlag<T extends keyof ExperimentalFeatures>(
32
34
  feature: T,
33
35
  ): ExperimentalFeatures[T] {
34
36
  return (
35
- (getResolvedMarimoConfig().experimental?.[
37
+ (getResolvedMarimoConfig()?.experimental?.[
36
38
  feature
37
39
  ] as ExperimentalFeatures[T]) ?? defaultValues[feature]
38
40
  );
@@ -1,10 +1,15 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
- import type { SQLTableListPreview, SQLTablePreview } from "../kernel/messages";
2
+ import type {
3
+ SQLTableListPreview,
4
+ SQLTablePreview,
5
+ ValidateSQLResult,
6
+ } from "../kernel/messages";
3
7
  import { DeferredRequestRegistry } from "../network/DeferredRequestRegistry";
4
8
  import { getRequestClient } from "../network/requests";
5
9
  import type {
6
10
  PreviewSQLTableListRequest,
7
11
  PreviewSQLTableRequest,
12
+ ValidateSQLRequest,
8
13
  } from "../network/types";
9
14
 
10
15
  // We make a request to the backend to preview the table, passing in Engine, DB, Schema, and Table
@@ -32,3 +37,14 @@ export const PreviewSQLTableList = new DeferredRequestRegistry<
32
37
  ...req,
33
38
  });
34
39
  });
40
+
41
+ export const ValidateSQL = new DeferredRequestRegistry<
42
+ Omit<ValidateSQLRequest, "requestId">,
43
+ ValidateSQLResult
44
+ >("validate-sql", async (requestId, req) => {
45
+ const client = getRequestClient();
46
+ await client.validateSQL({
47
+ requestId: requestId,
48
+ ...req,
49
+ });
50
+ });
@@ -452,10 +452,13 @@ export class HotkeyProvider implements IHotkeyProvider {
452
452
  return new HotkeyProvider(DEFAULT_HOT_KEY, { platform });
453
453
  }
454
454
 
455
+ private hotkeys: Record<HotkeyAction, Hotkey>;
456
+
455
457
  constructor(
456
- private hotkeys: Record<HotkeyAction, Hotkey>,
458
+ hotkeys: Record<HotkeyAction, Hotkey>,
457
459
  options: HotkeyProviderOptions = {},
458
460
  ) {
461
+ this.hotkeys = hotkeys;
459
462
  this.platform = options.platform ?? resolvePlatform();
460
463
  this.mod = this.platform === "mac" ? "Cmd" : "Ctrl";
461
464
  }
@@ -503,13 +506,14 @@ export class HotkeyProvider implements IHotkeyProvider {
503
506
  }
504
507
 
505
508
  export class OverridingHotkeyProvider extends HotkeyProvider {
509
+ private readonly overrides: Partial<Record<HotkeyAction, string | undefined>>;
510
+
506
511
  constructor(
507
- private readonly overrides: Partial<
508
- Record<HotkeyAction, string | undefined>
509
- >,
512
+ overrides: Partial<Record<HotkeyAction, string | undefined>>,
510
513
  options: HotkeyProviderOptions = {},
511
514
  ) {
512
515
  super(DEFAULT_HOT_KEY, options);
516
+ this.overrides = overrides;
513
517
  }
514
518
 
515
519
  override getHotkey(action: HotkeyAction): ResolvedHotkey {
@@ -0,0 +1,176 @@
1
+ /* Copyright 2024 Marimo. All rights reserved. */
2
+
3
+ import { cleanup, render } from "@testing-library/react";
4
+ import { createStore, Provider } from "jotai";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+ import { userConfigAtom } from "@/core/config/config";
7
+ import { parseUserConfig } from "@/core/config/config-schema";
8
+ import { LocaleProvider } from "../locale-provider";
9
+
10
+ // Mock navigator.language with a getter
11
+ let mockNavigatorLanguage: string | undefined;
12
+
13
+ Object.defineProperty(window, "navigator", {
14
+ value: {
15
+ get language() {
16
+ return mockNavigatorLanguage;
17
+ },
18
+ },
19
+ writable: true,
20
+ });
21
+
22
+ // Mock react-aria-components I18nProvider
23
+ vi.mock("react-aria-components", () => ({
24
+ I18nProvider: ({
25
+ children,
26
+ locale,
27
+ }: {
28
+ children: React.ReactNode;
29
+ locale: string;
30
+ }) => (
31
+ <div data-testid="i18n-provider" data-locale={locale}>
32
+ {children}
33
+ </div>
34
+ ),
35
+ }));
36
+
37
+ describe("LocaleProvider", () => {
38
+ beforeEach(() => {
39
+ // Reset the mock before each test
40
+ mockNavigatorLanguage = undefined;
41
+ });
42
+
43
+ afterEach(() => {
44
+ cleanup();
45
+ // Clear all mocks after each test
46
+ mockNavigatorLanguage = undefined;
47
+ vi.clearAllMocks();
48
+ });
49
+
50
+ it("should render I18nProvider without locale when locale is null", () => {
51
+ const store = createStore();
52
+ const config = parseUserConfig({ display: { locale: null } });
53
+ store.set(userConfigAtom, config);
54
+
55
+ const { getByTestId } = render(
56
+ <Provider store={store}>
57
+ <LocaleProvider>
58
+ <div>Test content</div>
59
+ </LocaleProvider>
60
+ </Provider>,
61
+ );
62
+
63
+ const i18nProvider = getByTestId("i18n-provider");
64
+ expect(i18nProvider).toBeInTheDocument();
65
+ expect(i18nProvider.dataset.locale).toBe(undefined);
66
+ expect(i18nProvider).toHaveTextContent("Test content");
67
+ });
68
+
69
+ it("should render I18nProvider without locale when locale is undefined", () => {
70
+ const store = createStore();
71
+ const config = parseUserConfig({ display: { locale: undefined } });
72
+ store.set(userConfigAtom, config);
73
+
74
+ const { getByTestId } = render(
75
+ <Provider store={store}>
76
+ <LocaleProvider>
77
+ <div>Test content</div>
78
+ </LocaleProvider>
79
+ </Provider>,
80
+ );
81
+
82
+ const i18nProvider = getByTestId("i18n-provider");
83
+ expect(i18nProvider).toBeInTheDocument();
84
+ expect(i18nProvider.dataset.locale).toBe(undefined);
85
+ expect(i18nProvider).toHaveTextContent("Test content");
86
+ });
87
+
88
+ it("should render I18nProvider with locale when locale is provided", () => {
89
+ const store = createStore();
90
+ const testLocale = "es-ES";
91
+ const config = parseUserConfig({ display: { locale: testLocale } });
92
+ store.set(userConfigAtom, config);
93
+
94
+ const { getByTestId } = render(
95
+ <Provider store={store}>
96
+ <LocaleProvider>
97
+ <div>Test content</div>
98
+ </LocaleProvider>
99
+ </Provider>,
100
+ );
101
+
102
+ const i18nProvider = getByTestId("i18n-provider");
103
+ expect(i18nProvider).toBeInTheDocument();
104
+ expect(i18nProvider.dataset.locale).toBe(testLocale);
105
+ expect(i18nProvider).toHaveTextContent("Test content");
106
+ });
107
+
108
+ it("should render I18nProvider with different locale values", () => {
109
+ const testCases = ["en-US", "fr-FR", "de-DE", "ja-JP"];
110
+
111
+ testCases.forEach((locale) => {
112
+ const store = createStore();
113
+ const config = parseUserConfig({ display: { locale } });
114
+ store.set(userConfigAtom, config);
115
+
116
+ const { getByTestId } = render(
117
+ <Provider store={store}>
118
+ <LocaleProvider>
119
+ <div>Test content for {locale}</div>
120
+ </LocaleProvider>
121
+ </Provider>,
122
+ );
123
+
124
+ const i18nProvider = getByTestId("i18n-provider");
125
+ expect(i18nProvider.dataset.locale).toBe(locale);
126
+ expect(i18nProvider).toHaveTextContent(`Test content for ${locale}`);
127
+
128
+ // Clean up after each iteration
129
+ cleanup();
130
+ });
131
+ });
132
+
133
+ it("should render children correctly", () => {
134
+ const store = createStore();
135
+ const config = parseUserConfig({ display: { locale: "en-US" } });
136
+ store.set(userConfigAtom, config);
137
+
138
+ const { getByText, getByRole } = render(
139
+ <Provider store={store}>
140
+ <LocaleProvider>
141
+ <div>
142
+ <h1>Test Heading</h1>
143
+ <p>Test paragraph</p>
144
+ <button type="button">Test Button</button>
145
+ </div>
146
+ </LocaleProvider>
147
+ </Provider>,
148
+ );
149
+
150
+ expect(getByText("Test Heading")).toBeInTheDocument();
151
+ expect(getByText("Test paragraph")).toBeInTheDocument();
152
+ expect(getByRole("button", { name: "Test Button" })).toBeInTheDocument();
153
+ });
154
+
155
+ it("should auto-detect locale when no locale is set in config", () => {
156
+ mockNavigatorLanguage = "de-DE";
157
+
158
+ const store = createStore();
159
+ const config = parseUserConfig({});
160
+ store.set(userConfigAtom, config);
161
+
162
+ const { getByTestId } = render(
163
+ <Provider store={store}>
164
+ <LocaleProvider>
165
+ <div>Test content</div>
166
+ </LocaleProvider>
167
+ </Provider>,
168
+ );
169
+
170
+ const i18nProvider = getByTestId("i18n-provider");
171
+ expect(i18nProvider).toBeInTheDocument();
172
+ // When no locale is specified in config, it should use navigator.language
173
+ expect(i18nProvider.dataset.locale).toBe("de-DE");
174
+ expect(i18nProvider).toHaveTextContent("Test content");
175
+ });
176
+ });