@marimo-team/islands 0.15.5 → 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 (217) hide show
  1. package/dist/{ConnectedDataExplorerComponent-CBeIYi8p.js → ConnectedDataExplorerComponent-DyqLQGPc.js} +1567 -1544
  2. package/dist/{ImageComparisonComponent-Bk0a0xBq.js → ImageComparisonComponent-CQDGJfUA.js} +1 -1
  3. package/dist/{_baseUniq-utU5_Vu-.js → _baseUniq-B2Nna6Kt.js} +1 -1
  4. package/dist/{any-language-editor-PrUUh2lr.js → any-language-editor-D-wq0tOG.js} +1 -1
  5. package/dist/{architectureDiagram-W76B3OCA-D-vOp0UU.js → architectureDiagram-W76B3OCA-C6tdnMBf.js} +4 -4
  6. package/dist/assets/{worker-BcG8m3h5.js → worker-B0C57BK8.js} +40 -38
  7. package/dist/{blockDiagram-QIGZ2CNN-IG-z8q8A.js → blockDiagram-QIGZ2CNN-IagL8LCN.js} +5 -5
  8. package/dist/{c4Diagram-FPNF74CW-5AEXIX3t.js → c4Diagram-FPNF74CW-D3_lIWUP.js} +2 -2
  9. package/dist/{channel-ECVsTGGL.js → channel-DCJI_DKk.js} +1 -1
  10. package/dist/{chunk-4BX2VUAB-DfJcd9e-.js → chunk-4BX2VUAB-B2DrODwN.js} +1 -1
  11. package/dist/{chunk-55IACEB6-BwT8MejR.js → chunk-55IACEB6-BUWDsQ-t.js} +1 -1
  12. package/dist/{chunk-FMBD7UC4-DW7uxNR6.js → chunk-FMBD7UC4-BExPNFv1.js} +1 -1
  13. package/dist/{chunk-K7UQS3LO-BGn2ZPDQ.js → chunk-K7UQS3LO-Cixi-Yko.js} +4 -4
  14. package/dist/{chunk-QN33PNHL-BcIbOumv.js → chunk-QN33PNHL-B83MtvER.js} +1 -1
  15. package/dist/{chunk-QZHKN3VN-CMSnhk6x.js → chunk-QZHKN3VN-CXvbu85X.js} +1 -1
  16. package/dist/{chunk-TVAH2DTR-CZF2JRya.js → chunk-TVAH2DTR-CpiumCHg.js} +3 -3
  17. package/dist/{chunk-TZMSLE5B-BHzN_BY6.js → chunk-TZMSLE5B-DIzaZjcI.js} +1 -1
  18. package/dist/{classDiagram-v2-RKCZMP56-2H7MseyB.js → classDiagram-KNZD7YFC-DyN5HPdk.js} +2 -2
  19. package/dist/{classDiagram-KNZD7YFC-2H7MseyB.js → classDiagram-v2-RKCZMP56-DyN5HPdk.js} +2 -2
  20. package/dist/{clone-DKQcSK7N.js → clone-DrJYap2i.js} +1 -1
  21. package/dist/{cose-bilkent-S5V4N54A-CgvKFxTr.js → cose-bilkent-S5V4N54A-D39b4WrQ.js} +2 -2
  22. package/dist/{dagre-5GWH7T2D-VNFIipzt.js → dagre-5GWH7T2D-BLjRxDpS.js} +6 -6
  23. package/dist/{data-grid-overlay-editor-XdqkKCVx.js → data-grid-overlay-editor-DTALqerV.js} +2 -2
  24. package/dist/{diagram-N5W7TBWH-D1s8h-eH.js → diagram-N5W7TBWH-MM8AIKGR.js} +5 -5
  25. package/dist/{diagram-QEK2KX5R-DOa-AstT.js → diagram-QEK2KX5R-BZGarWuJ.js} +3 -3
  26. package/dist/{diagram-S2PKOQOG-CFZ-Y2zi.js → diagram-S2PKOQOG-CnPinN9Q.js} +3 -3
  27. package/dist/{dockerfile-zE-2DWBS.js → dockerfile-U8DnCJ4X.js} +1 -1
  28. package/dist/{erDiagram-AWTI2OKA-WxUYJfbS.js → erDiagram-AWTI2OKA-CvDVbxOO.js} +4 -4
  29. package/dist/{flowDiagram-PVAE7QVJ-dDZH2O1W.js → flowDiagram-PVAE7QVJ-C2uuBTZS.js} +5 -5
  30. package/dist/{ganttDiagram-OWAHRB6G-D3CCqPQq.js → ganttDiagram-OWAHRB6G-BEff10RF.js} +4 -4
  31. package/dist/{gitGraphDiagram-NY62KEGX-BHFylEwc.js → gitGraphDiagram-NY62KEGX-wggu0kb2.js} +4 -4
  32. package/dist/{glide-data-editor-D0aJSGV_.js → glide-data-editor-Bqh5_dzJ.js} +3 -3
  33. package/dist/{graph-BPGEu6c8.js → graph-DKpp_wzf.js} +3 -3
  34. package/dist/{index-HtOEKQ3O.js → index-4XruEJkp.js} +1 -1
  35. package/dist/{index-eDB61tLS.js → index-DW0BCGJE.js} +1 -1
  36. package/dist/{index-DotQhzoN.js → index-DdfF_cLK.js} +1 -1
  37. package/dist/{index-Bx2b23rX.js → index-DzJ_YPCG.js} +3 -3
  38. package/dist/{infoDiagram-STP46IZ2-DWhhqGPi.js → infoDiagram-STP46IZ2-DF7KW-Op.js} +2 -2
  39. package/dist/{journeyDiagram-BIP6EPQ6-CU8FpryL.js → journeyDiagram-BIP6EPQ6-B_jmhmqd.js} +3 -3
  40. package/dist/{kanban-definition-6OIFK2YF-CWhF_a4g.js → kanban-definition-6OIFK2YF-B-M9FTyw.js} +2 -2
  41. package/dist/{layout-DGonEvAZ.js → layout-C4oVYZZD.js} +4 -4
  42. package/dist/{linear-Cww2a6nQ.js → linear-C-HCGr0T.js} +1 -1
  43. package/dist/{main-Bc0LY9fB.js → main-B9x2-9f2.js} +93798 -93495
  44. package/dist/main.js +1 -1
  45. package/dist/{mermaid-DpJuOhRr.js → mermaid-BE4cM3Qs.js} +30 -30
  46. package/dist/{min-CFQjsG4L.js → min-DTpHJ698.js} +2 -2
  47. package/dist/{mindmap-definition-Q6HEUPPD-K513Ef1t.js → mindmap-definition-Q6HEUPPD-Cpd-hO1E.js} +3 -3
  48. package/dist/{number-overlay-editor-DuSchUfE.js → number-overlay-editor-CvURA2Ud.js} +2 -2
  49. package/dist/{pieDiagram-ADFJNKIX-DAIIUJJO.js → pieDiagram-ADFJNKIX-D9f_f6fn.js} +3 -3
  50. package/dist/{quadrantDiagram-LMRXKWRM-yuf-j7Os.js → quadrantDiagram-LMRXKWRM-DgllE7xw.js} +2 -2
  51. package/dist/{react-plotly-B378DZ9U.js → react-plotly-BU-JRJSi.js} +1 -1
  52. package/dist/{requirementDiagram-4UW4RH46-BBWvEl6q.js → requirementDiagram-4UW4RH46-Dk_G8eUb.js} +3 -3
  53. package/dist/{sankeyDiagram-GR3RE2ED-B_TwV-dS.js → sankeyDiagram-GR3RE2ED-BhLIhDc1.js} +1 -1
  54. package/dist/{sequenceDiagram-C3RYC4MD-BVC6lltp.js → sequenceDiagram-C3RYC4MD-DHoZdMFJ.js} +3 -3
  55. package/dist/{slides-component-CPX3S0Y9.js → slides-component-DXAgdf7K.js} +2 -2
  56. package/dist/{stateDiagram-KXAO66HF-BCU1tYTD.js → stateDiagram-KXAO66HF-C1Ie-7Xf.js} +4 -4
  57. package/dist/{stateDiagram-v2-UMBNRL4Z-BdvN6wTu.js → stateDiagram-v2-UMBNRL4Z--CRuIHtM.js} +2 -2
  58. package/dist/style.css +1 -1
  59. package/dist/{time-CSIip6fV.js → time-yQjlGPwa.js} +2 -2
  60. package/dist/{timeline-definition-XQNQX7LJ-CCxCPNQI.js → timeline-definition-XQNQX7LJ-D_PjxB1B.js} +1 -1
  61. package/dist/{treemap-75Q7IDZK-Du6v0BzD.js → treemap-75Q7IDZK--NYqQjUZ.js} +134 -134
  62. package/dist/{vega-component-Da93sTnp.js → vega-component-CCUOMM5K.js} +2 -2
  63. package/dist/{xychartDiagram-6GGTOJPD-Oq6xaZKR.js → xychartDiagram-6GGTOJPD-WLKsEnzs.js} +2 -2
  64. package/package.json +10 -5
  65. package/src/__tests__/mocks.ts +43 -0
  66. package/src/components/app-config/user-config-form.tsx +78 -1
  67. package/src/components/chat/acp/__tests__/__snapshots__/prompt.test.ts.snap +116 -65
  68. package/src/components/chat/acp/__tests__/atoms.test.ts +1 -1
  69. package/src/components/chat/acp/__tests__/context-utils.test.ts +222 -0
  70. package/src/components/chat/acp/__tests__/prompt.test.ts +1 -1
  71. package/src/components/chat/acp/__tests__/state.test.ts +38 -42
  72. package/src/components/chat/acp/agent-docs.tsx +33 -6
  73. package/src/components/chat/acp/agent-panel.css +0 -18
  74. package/src/components/chat/acp/agent-panel.tsx +394 -72
  75. package/src/components/chat/acp/agent-selector.tsx +7 -1
  76. package/src/components/chat/acp/blocks.tsx +40 -10
  77. package/src/components/chat/acp/common.tsx +10 -2
  78. package/src/components/chat/acp/context-utils.ts +127 -0
  79. package/src/components/chat/acp/prompt.ts +96 -53
  80. package/src/components/chat/acp/state.ts +1 -1
  81. package/src/components/chat/acp/types.ts +8 -0
  82. package/src/components/chat/chat-panel.tsx +28 -89
  83. package/src/components/chat/chat-utils.ts +127 -1
  84. package/src/components/chat/markdown-renderer.css +39 -0
  85. package/src/components/chat/markdown-renderer.tsx +12 -47
  86. package/src/components/chat/tool-call-accordion.tsx +148 -26
  87. package/src/components/data-table/SearchBar.tsx +8 -7
  88. package/src/components/data-table/__tests__/column_formatting.test.ts +50 -35
  89. package/src/components/data-table/__tests__/data-table.test.tsx +39 -1
  90. package/src/components/data-table/cell-hover-template/feature.ts +14 -0
  91. package/src/components/data-table/cell-hover-template/types.ts +11 -0
  92. package/src/components/data-table/charts/components/form-fields.tsx +41 -37
  93. package/src/components/data-table/charts/forms/common-chart.tsx +2 -2
  94. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +5 -2
  95. package/src/components/data-table/column-formatting/feature.ts +62 -29
  96. package/src/components/data-table/column-formatting/types.ts +1 -0
  97. package/src/components/data-table/column-header.tsx +3 -1
  98. package/src/components/data-table/column-summary/chart-spec-model.tsx +24 -7
  99. package/src/components/data-table/column-summary/column-summary.tsx +18 -9
  100. package/src/components/data-table/columns.tsx +42 -18
  101. package/src/components/data-table/data-table.tsx +10 -2
  102. package/src/components/data-table/date-popover.tsx +85 -75
  103. package/src/components/data-table/filter-pills.tsx +14 -9
  104. package/src/components/data-table/header-items.tsx +5 -1
  105. package/src/components/data-table/pagination.tsx +20 -13
  106. package/src/components/data-table/renderers.tsx +28 -0
  107. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +10 -8
  108. package/src/components/datasources/column-preview.tsx +6 -2
  109. package/src/components/datasources/datasources.tsx +8 -12
  110. package/src/components/editor/Cell.tsx +6 -0
  111. package/src/components/editor/actions/name-cell-input.tsx +6 -1
  112. package/src/components/editor/actions/useCellActionButton.tsx +3 -1
  113. package/src/components/editor/ai/__tests__/completion-utils.test.ts +178 -1
  114. package/src/components/editor/ai/add-cell-with-ai.tsx +68 -66
  115. package/src/components/editor/ai/ai-completion-editor.tsx +29 -26
  116. package/src/components/editor/ai/completion-handlers.tsx +44 -6
  117. package/src/components/editor/ai/completion-utils.ts +92 -0
  118. package/src/components/editor/ai/transport/chat-transport.tsx +39 -0
  119. package/src/components/editor/cell/CellStatus.tsx +23 -20
  120. package/src/components/editor/cell/CreateCellButton.tsx +3 -4
  121. package/src/components/editor/cell/StagedAICell.tsx +51 -0
  122. package/src/components/editor/cell/cell-actions.tsx +2 -1
  123. package/src/components/editor/cell/code/language-toggle.tsx +3 -4
  124. package/src/components/editor/chrome/wrapper/footer-items/machine-stats.tsx +39 -28
  125. package/src/components/editor/controls/notebook-menu-dropdown.tsx +4 -2
  126. package/src/components/editor/file-tree/requesting-tree.tsx +14 -8
  127. package/src/components/editor/renderers/CellArray.tsx +3 -4
  128. package/src/components/editor/renderers/slides-layout/slides-layout.tsx +3 -3
  129. package/src/components/editor/renderers/slides-layout/types.ts +1 -0
  130. package/src/components/pages/home-page.tsx +4 -1
  131. package/src/components/slides/slides-component.tsx +1 -1
  132. package/src/components/slides/slides.css +6 -0
  133. package/src/components/terminal/__tests__/state.test.ts +207 -0
  134. package/src/components/terminal/hooks.ts +41 -0
  135. package/src/components/terminal/state.ts +75 -0
  136. package/src/components/terminal/terminal.tsx +334 -13
  137. package/src/components/terminal/theme.tsx +57 -0
  138. package/src/components/tracing/tracing-spec.ts +5 -4
  139. package/src/components/ui/range-slider.tsx +4 -2
  140. package/src/components/ui/slider.tsx +3 -1
  141. package/src/components/variables/variables-table.tsx +3 -0
  142. package/src/core/MarimoApp.tsx +9 -6
  143. package/src/core/ai/__tests__/staged-cells.test.ts +356 -0
  144. package/src/core/ai/context/__tests__/registry.test.ts +6 -4
  145. package/src/core/ai/context/providers/cell-output.ts +3 -2
  146. package/src/core/ai/context/providers/error.ts +3 -1
  147. package/src/core/ai/context/providers/file.ts +7 -2
  148. package/src/core/ai/context/providers/tables.ts +3 -2
  149. package/src/core/ai/context/providers/variable.ts +6 -4
  150. package/src/core/ai/staged-cells.ts +241 -0
  151. package/src/core/cells/__tests__/add-missing-import.test.ts +67 -22
  152. package/src/core/cells/add-missing-import.ts +24 -7
  153. package/src/core/cells/cells.ts +27 -28
  154. package/src/core/cells/logs.ts +1 -1
  155. package/src/core/codemirror/find-replace/search-highlight.ts +3 -1
  156. package/src/core/codemirror/language/LanguageAdapters.ts +9 -3
  157. package/src/core/codemirror/lsp/federated-lsp.ts +1 -1
  158. package/src/core/codemirror/lsp/notebook-lsp.ts +8 -2
  159. package/src/core/codemirror/readonly/__tests__/extension.test.ts +1 -1
  160. package/src/core/codemirror/rtc/loro/awareness.ts +52 -17
  161. package/src/core/codemirror/rtc/loro/sync.ts +12 -4
  162. package/src/core/config/config-schema.ts +1 -0
  163. package/src/core/config/config.ts +4 -0
  164. package/src/core/hotkeys/hotkeys.ts +8 -4
  165. package/src/core/i18n/__tests__/locale-provider.test.tsx +176 -0
  166. package/src/core/i18n/locale-provider.tsx +35 -0
  167. package/src/core/i18n/with-locale.tsx +12 -0
  168. package/src/core/islands/components/web-components.tsx +13 -10
  169. package/src/core/islands/main.ts +2 -2
  170. package/src/core/kernel/RuntimeState.ts +4 -1
  171. package/src/core/kernel/messages.ts +8 -12
  172. package/src/core/network/DeferredRequestRegistry.ts +16 -4
  173. package/src/core/runtime/runtime.ts +5 -4
  174. package/src/core/saving/__tests__/filename.test.ts +37 -0
  175. package/src/core/static/__tests__/download-html.test.ts +43 -1
  176. package/src/core/wasm/bridge.ts +5 -1
  177. package/src/core/wasm/store.ts +4 -1
  178. package/src/core/wasm/worker/message-buffer.ts +3 -2
  179. package/src/core/websocket/types.ts +22 -16
  180. package/src/core/websocket/useMarimoWebSocket.tsx +2 -2
  181. package/src/css/app/Cell.css +11 -0
  182. package/src/hooks/useFormatting.ts +97 -0
  183. package/src/hooks/useTimer.ts +8 -5
  184. package/src/plugins/core/RenderHTML.tsx +36 -2
  185. package/src/plugins/core/__test__/RenderHTML.test.ts +72 -0
  186. package/src/plugins/core/registerReactComponent.tsx +44 -10
  187. package/src/plugins/impl/DataTablePlugin.tsx +4 -0
  188. package/src/plugins/impl/FileBrowserPlugin.tsx +8 -2
  189. package/src/plugins/impl/RangeSliderPlugin.tsx +5 -3
  190. package/src/plugins/impl/SliderPlugin.tsx +3 -1
  191. package/src/plugins/impl/anywidget/model.ts +16 -5
  192. package/src/plugins/impl/data-editor/types.ts +7 -5
  193. package/src/plugins/impl/data-explorer/components/column-summary.tsx +20 -13
  194. package/src/plugins/impl/panel/utils.ts +6 -4
  195. package/src/plugins/layout/OutlinePlugin.tsx +69 -0
  196. package/src/plugins/layout/StatPlugin.tsx +4 -1
  197. package/src/plugins/plugins.ts +2 -0
  198. package/src/stories/cell.stories.tsx +1 -1
  199. package/src/stories/layout/vertical/one-column.stories.tsx +1 -1
  200. package/src/utils/__tests__/cell-urls.test.ts +29 -0
  201. package/src/utils/__tests__/dates.test.ts +45 -24
  202. package/src/utils/__tests__/filenames.test.ts +18 -0
  203. package/src/utils/__tests__/numbers.test.ts +42 -30
  204. package/src/utils/__tests__/once.test.ts +187 -0
  205. package/src/utils/__tests__/path.test.ts +38 -0
  206. package/src/utils/__tests__/urls.test.ts +56 -1
  207. package/src/utils/dates.ts +15 -10
  208. package/src/utils/edit-distance.ts +8 -6
  209. package/src/utils/errors.ts +9 -0
  210. package/src/utils/id-tree.tsx +21 -10
  211. package/src/utils/localStorage.ts +13 -4
  212. package/src/utils/numbers.ts +11 -11
  213. package/src/utils/once.ts +32 -0
  214. package/src/utils/paths.ts +4 -1
  215. package/src/utils/pluralize.ts +12 -5
  216. package/src/utils/python-poet/poet.ts +30 -15
  217. package/src/utils/time.ts +5 -1
@@ -0,0 +1,97 @@
1
+ /* Copyright 2024 Marimo. All rights reserved. */
2
+
3
+ import { useCallback } from "react";
4
+ import { useLocale } from "react-aria";
5
+ import { getShortTimeZone, prettyDate, timeAgo } from "@/utils/dates";
6
+ import {
7
+ prettyEngineeringNumber,
8
+ prettyNumber,
9
+ prettyScientificNumber,
10
+ } from "@/utils/numbers";
11
+
12
+ /**
13
+ * Hook that provides locale-aware number formatting using the prettyNumber utility
14
+ */
15
+ export function usePrettyNumber() {
16
+ const { locale } = useLocale();
17
+
18
+ return useCallback(
19
+ (value: unknown): string => {
20
+ return prettyNumber(value, locale);
21
+ },
22
+ [locale],
23
+ );
24
+ }
25
+
26
+ /**
27
+ * Hook that provides locale-aware scientific number formatting
28
+ */
29
+ export function usePrettyScientificNumber() {
30
+ const { locale } = useLocale();
31
+
32
+ return useCallback(
33
+ (value: number, opts: { shouldRound?: boolean } = {}): string => {
34
+ return prettyScientificNumber(value, { ...opts, locale });
35
+ },
36
+ [locale],
37
+ );
38
+ }
39
+
40
+ /**
41
+ * Hook that provides locale-aware engineering number formatting
42
+ */
43
+ export function usePrettyEngineeringNumber() {
44
+ const { locale } = useLocale();
45
+
46
+ return useCallback(
47
+ (value: number): string => {
48
+ return prettyEngineeringNumber(value, locale);
49
+ },
50
+ [locale],
51
+ );
52
+ }
53
+
54
+ /**
55
+ * Hook that provides locale-aware date formatting
56
+ */
57
+ export function usePrettyDate() {
58
+ const { locale } = useLocale();
59
+
60
+ return useCallback(
61
+ (
62
+ value: string | number | null | undefined,
63
+ type: "date" | "datetime",
64
+ ): string => {
65
+ return prettyDate(value, type, locale);
66
+ },
67
+ [locale],
68
+ );
69
+ }
70
+
71
+ /**
72
+ * Hook that provides locale-aware relative time formatting
73
+ */
74
+ export function useTimeAgo() {
75
+ const { locale } = useLocale();
76
+
77
+ return useCallback(
78
+ (value: string | number | null | undefined): string => {
79
+ return timeAgo(value, locale);
80
+ },
81
+ [locale],
82
+ );
83
+ }
84
+
85
+ /**
86
+ * Hook that provides locale-aware timezone abbreviation
87
+ */
88
+ export function useShortTimeZone() {
89
+ const { locale } = useLocale();
90
+
91
+ return useCallback(
92
+ (timezone: string): string => {
93
+ return getShortTimeZone(timezone, locale);
94
+ },
95
+ [locale],
96
+ );
97
+ }
@@ -1,6 +1,7 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
2
 
3
3
  import { useEffect, useRef, useState } from "react";
4
+ import { useNumberFormatter } from "react-aria";
4
5
  import useEvent from "react-use-event-hook";
5
6
 
6
7
  /**
@@ -11,6 +12,12 @@ export function useTimer() {
11
12
  const [time, setTime] = useState(0);
12
13
  const interval = useRef<number>(undefined);
13
14
 
15
+ // one decimal place, exactly
16
+ const numberFormatter = useNumberFormatter({
17
+ minimumFractionDigits: 1,
18
+ maximumFractionDigits: 1,
19
+ });
20
+
14
21
  const start = useEvent(() => {
15
22
  interval.current = window.setInterval(() => {
16
23
  setTime((time) => time + 0.1);
@@ -36,11 +43,7 @@ export function useTimer() {
36
43
  }, []);
37
44
 
38
45
  return {
39
- // one decimal place, exactly
40
- time: new Intl.NumberFormat("en-US", {
41
- minimumFractionDigits: 1,
42
- maximumFractionDigits: 1,
43
- }).format(time),
46
+ time: numberFormatter.format(time),
44
47
  start,
45
48
  stop,
46
49
  clear,
@@ -5,7 +5,7 @@ import parse, {
5
5
  Element,
6
6
  type HTMLReactParserOptions,
7
7
  } from "html-react-parser";
8
- import React, { type JSX, type ReactNode, useId } from "react";
8
+ import React, { isValidElement, type JSX, type ReactNode, useId } from "react";
9
9
  import { CopyClipboardIcon } from "@/components/icons/copy-icon";
10
10
 
11
11
  type ReplacementFn = NonNullable<HTMLReactParserOptions["replace"]>;
@@ -23,6 +23,36 @@ const replaceValidTags = (domNode: DOMNode) => {
23
23
  }
24
24
  };
25
25
 
26
+ const removeWrappingBodyTags: TransformFn = (
27
+ reactNode: ReactNode,
28
+ domNode: DOMNode,
29
+ ) => {
30
+ // Remove body tags and just render their children
31
+ if (domNode instanceof Element && domNode.name === "body") {
32
+ if (isValidElement(reactNode) && "props" in reactNode) {
33
+ const props = reactNode.props as { children?: ReactNode };
34
+ const children = props.children;
35
+ return <>{children}</>; // eslint-disable-line react/jsx-no-useless-fragment
36
+ }
37
+ return;
38
+ }
39
+ };
40
+
41
+ const removeWrappingHtmlTags: TransformFn = (
42
+ reactNode: ReactNode,
43
+ domNode: DOMNode,
44
+ ) => {
45
+ // Remove html tags and just render their children
46
+ if (domNode instanceof Element && domNode.name === "html") {
47
+ if (isValidElement(reactNode) && "props" in reactNode) {
48
+ const props = reactNode.props as { children?: ReactNode };
49
+ const children = props.children;
50
+ return <>{children}</>; // eslint-disable-line react/jsx-no-useless-fragment
51
+ }
52
+ return;
53
+ }
54
+ };
55
+
26
56
  const replaceValidIframes = (domNode: DOMNode) => {
27
57
  // For iframe, we just want to use dangerouslySetInnerHTML so:
28
58
  // 1) we can remount the iframe when the src changes
@@ -110,7 +140,11 @@ export const renderHTML = ({ html, additionalReplacements = [] }: Options) => {
110
140
  ...additionalReplacements,
111
141
  ];
112
142
 
113
- const transformFunctions: TransformFn[] = [addCopyButtonToCodehilite];
143
+ const transformFunctions: TransformFn[] = [
144
+ addCopyButtonToCodehilite,
145
+ removeWrappingBodyTags,
146
+ removeWrappingHtmlTags,
147
+ ];
114
148
 
115
149
  return parse(html, {
116
150
  replace: (domNode: DOMNode, index: number) => {
@@ -121,6 +121,78 @@ describe("RenderHTML", () => {
121
121
  </p>
122
122
  `);
123
123
  });
124
+
125
+ test("removes body tags but preserves children", () => {
126
+ const html = "<body><h1>Hello</h1><p>World</p></body>";
127
+ expect(renderHTML({ html })).toMatchInlineSnapshot(`
128
+ <React.Fragment>
129
+ <h1>
130
+ Hello
131
+ </h1>
132
+ <p>
133
+ World
134
+ </p>
135
+ </React.Fragment>
136
+ `);
137
+ });
138
+
139
+ test("removes nested body tags", () => {
140
+ const html = "<div><body><span>Content</span></body></div>";
141
+ expect(renderHTML({ html })).toMatchInlineSnapshot(`
142
+ <div>
143
+ <React.Fragment>
144
+ <span>
145
+ Content
146
+ </span>
147
+ </React.Fragment>
148
+ </div>
149
+ `);
150
+ });
151
+
152
+ test("removes html tags but preserves children", () => {
153
+ const html =
154
+ "<html><head><title>Test</title></head><body><p>Content</p></body></html>";
155
+ expect(renderHTML({ html })).toMatchInlineSnapshot(`
156
+ <React.Fragment>
157
+ <head>
158
+ <title>
159
+ Test
160
+ </title>
161
+ </head>
162
+ <React.Fragment>
163
+ <p>
164
+ Content
165
+ </p>
166
+ </React.Fragment>
167
+ </React.Fragment>
168
+ `);
169
+ });
170
+
171
+ test("removes nested html tags", () => {
172
+ const html = "<div><html><span>Content</span></html></div>";
173
+ expect(renderHTML({ html })).toMatchInlineSnapshot(`
174
+ <div>
175
+ <React.Fragment>
176
+ <span>
177
+ Content
178
+ </span>
179
+ </React.Fragment>
180
+ </div>
181
+ `);
182
+ });
183
+
184
+ test("remove nested body in html", () => {
185
+ const html = "<html><body><span>Content</span></body></html>";
186
+ expect(renderHTML({ html })).toMatchInlineSnapshot(`
187
+ <React.Fragment>
188
+ <React.Fragment>
189
+ <span>
190
+ Content
191
+ </span>
192
+ </React.Fragment>
193
+ </React.Fragment>
194
+ `);
195
+ });
124
196
  });
125
197
 
126
198
  describe("RenderHTML with < nad >", () => {
@@ -1,4 +1,5 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
+
2
3
  /* eslint-disable unicorn/prefer-spread */
3
4
  /**
4
5
  * WebComponent Factory for React Components
@@ -7,6 +8,7 @@
7
8
  * component. The factory handles the logic of communicating UI element values
8
9
  * to and from the rest of marimo.
9
10
  */
11
+ import { Provider } from "jotai";
10
12
  import React, {
11
13
  createRef,
12
14
  type JSX,
@@ -21,16 +23,22 @@ import React, {
21
23
  import ReactDOM, { type Root } from "react-dom/client";
22
24
  import useEvent from "react-use-event-hook";
23
25
  import type { ZodSchema } from "zod";
26
+ import { notebookAtom } from "@/core/cells/cells.ts";
27
+ import { findCellId } from "@/core/cells/ids.ts";
28
+ import { isUninstantiated } from "@/core/cells/utils";
24
29
  import { createInputEvent, MarimoValueUpdateEvent } from "@/core/dom/events";
25
30
  import { getUIElementObjectId } from "@/core/dom/ui-element";
26
31
  import { UIElementRegistry } from "@/core/dom/uiregistry";
27
32
  import { FUNCTIONS_REGISTRY } from "@/core/functions/FunctionRegistry";
33
+ import { LocaleProvider } from "@/core/i18n/locale-provider";
34
+ import { store } from "@/core/state/jotai";
28
35
  import {
29
36
  type HTMLElementNotDerivedFromRef,
30
37
  useEventListener,
31
38
  } from "@/hooks/useEventListener";
32
39
  import { StyleNamespace } from "@/theme/namespace";
33
40
  import { useTheme } from "@/theme/useTheme";
41
+ import { CellNotInitializedError } from "@/utils/errors.ts";
34
42
  import { Functions } from "@/utils/functions";
35
43
  import { shallowCompare } from "@/utils/shallow-compare";
36
44
  import { defineCustomElement } from "../../core/dom/defineCustomElement";
@@ -78,6 +86,7 @@ interface PluginSlotProps<T> {
78
86
  }
79
87
 
80
88
  /* Handles synchronization of value on behalf of the component */
89
+
81
90
  // eslint-disable-next-line react/function-component-definition
82
91
  function PluginSlotInternal<T>(
83
92
  { hostElement, plugin, children, getInitialValue }: PluginSlotProps<T>,
@@ -172,6 +181,27 @@ function PluginSlotInternal<T>(
172
181
  );
173
182
  const objectId = getUIElementObjectId(hostElement);
174
183
  invariant(objectId, "Object ID should exist");
184
+
185
+ const cellId = findCellId(hostElement);
186
+ invariant(cellId, "Cell ID should exist");
187
+
188
+ const notebookState = store.get(notebookAtom);
189
+ const cellRuntime = notebookState.cellRuntime[cellId];
190
+ const cellData = notebookState.cellData[cellId];
191
+
192
+ const cellNotInitialized = isUninstantiated({
193
+ executionTime:
194
+ cellRuntime.runElapsedTimeMs ?? cellData.lastExecutionTime,
195
+ status: cellRuntime.status,
196
+ errored: cellRuntime.errored,
197
+ interrupted: cellRuntime.interrupted,
198
+ stopped: cellRuntime.stopped,
199
+ });
200
+
201
+ if (cellNotInitialized) {
202
+ throw new CellNotInitializedError();
203
+ }
204
+
175
205
  const response = await FUNCTIONS_REGISTRY.request({
176
206
  args: prettyParse(input, args[0]),
177
207
  functionName: key,
@@ -335,16 +365,20 @@ export function registerReactComponent<T>(plugin: IPlugin<T, unknown>): void {
335
365
 
336
366
  invariant(this.root, "Root must be defined");
337
367
  this.root.render(
338
- <PluginSlot
339
- hostElement={this}
340
- plugin={plugin}
341
- ref={this.pluginRef}
342
- getInitialValue={() => {
343
- return parseInitialValue(this, UIElementRegistry.INSTANCE);
344
- }}
345
- >
346
- {this.getChildren()}
347
- </PluginSlot>,
368
+ <Provider store={store}>
369
+ <LocaleProvider>
370
+ <PluginSlot
371
+ hostElement={this}
372
+ plugin={plugin}
373
+ ref={this.pluginRef}
374
+ getInitialValue={() => {
375
+ return parseInitialValue(this, UIElementRegistry.INSTANCE);
376
+ }}
377
+ >
378
+ {this.getChildren()}
379
+ </PluginSlot>
380
+ </LocaleProvider>
381
+ </Provider>,
348
382
  );
349
383
  }
350
384
 
@@ -254,6 +254,7 @@ export const DataTablePlugin = createPlugin<S>("marimo-table")
254
254
  maxColumns: z.union([z.number(), z.literal("all")]).default("all"),
255
255
  hasStableRowId: z.boolean().default(false),
256
256
  cellStyles: z.record(z.record(z.object({}).passthrough())).optional(),
257
+ hoverTemplate: z.string().optional(),
257
258
  // Whether to load the data lazily.
258
259
  lazy: z.boolean().default(false),
259
260
  // If lazy, this will preload the first page of data
@@ -385,6 +386,7 @@ interface DataTableProps<T> extends Data<T>, DataTableFunctions {
385
386
  // Filters
386
387
  enableFilters?: boolean;
387
388
  cellStyles?: CellStyleState | null;
389
+ hoverTemplate?: string | null;
388
390
  toggleDisplayHeader?: () => void;
389
391
  host: HTMLElement;
390
392
  cellId?: CellId | null;
@@ -707,6 +709,7 @@ const DataTableComponent = ({
707
709
  totalColumns,
708
710
  get_row_ids,
709
711
  cellStyles,
712
+ hoverTemplate,
710
713
  toggleDisplayHeader,
711
714
  calculate_top_k_rows,
712
715
  preview_column,
@@ -904,6 +907,7 @@ const DataTableComponent = ({
904
907
  rowSelection={rowSelection}
905
908
  cellSelection={cellSelection}
906
909
  cellStyling={cellStyles}
910
+ hoverTemplate={hoverTemplate}
907
911
  downloadAs={showDownload ? downloadAs : undefined}
908
912
  enableSearch={enableSearch}
909
913
  searchQuery={searchQuery}
@@ -12,7 +12,7 @@ import { Button } from "@/components/ui/button";
12
12
  import { Checkbox } from "@/components/ui/checkbox";
13
13
  import { Label } from "@/components/ui/label";
14
14
  import { NativeSelect } from "@/components/ui/native-select";
15
- import { Table, TableCell, TableRow } from "@/components/ui/table";
15
+ import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
16
16
  import { toast } from "@/components/ui/use-toast";
17
17
  import { useAsyncData } from "@/hooks/useAsyncData";
18
18
  import { useInternalStateWithSync } from "@/hooks/useInternalStateWithSync";
@@ -158,6 +158,10 @@ export const FileBrowser = ({
158
158
  return null;
159
159
  }
160
160
 
161
+ if (!data && error) {
162
+ return <Banner kind="danger">{error.message}</Banner>;
163
+ }
164
+
161
165
  let { files } = data || {};
162
166
  if (files === undefined) {
163
167
  files = [];
@@ -458,7 +462,9 @@ export const FileBrowser = ({
458
462
  className="mt-3 overflow-y-auto w-full border"
459
463
  style={{ height: "14rem" }}
460
464
  >
461
- <Table className="cursor-pointer table-fixed">{fileRows}</Table>
465
+ <Table className="cursor-pointer table-fixed">
466
+ <TableBody>{fileRows}</TableBody>
467
+ </Table>
462
468
  </div>
463
469
  <div className="mt-4">
464
470
  {value.length > 0 && (
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { isEqual } from "lodash-es";
4
4
  import { type JSX, useEffect, useId, useState } from "react";
5
+ import { useLocale } from "react-aria";
5
6
  import { z } from "zod";
6
7
  import { cn } from "@/utils/cn";
7
8
  import { prettyScientificNumber } from "@/utils/numbers";
@@ -82,6 +83,7 @@ const RangeSliderComponent = ({
82
83
  valueMap,
83
84
  }: RangeSliderProps): JSX.Element => {
84
85
  const id = useId();
86
+ const { locale } = useLocale();
85
87
 
86
88
  // Hold internal value
87
89
  const [internalValue, setInternalValue] = useState(value);
@@ -150,9 +152,9 @@ const RangeSliderComponent = ({
150
152
  />
151
153
  {showValue && (
152
154
  <div className="text-xs text-muted-foreground min-w-[16px]">
153
- {`${prettyScientificNumber(
154
- valueMap(internalValue[0]),
155
- )}, ${prettyScientificNumber(valueMap(internalValue[1]))}`}
155
+ {`${prettyScientificNumber(valueMap(internalValue[0]), {
156
+ locale,
157
+ })}, ${prettyScientificNumber(valueMap(internalValue[1]), { locale })}`}
156
158
  </div>
157
159
  )}
158
160
  </div>
@@ -1,5 +1,6 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
2
  import { type JSX, useEffect, useId, useState } from "react";
3
+ import { useLocale } from "react-aria";
3
4
  import { z } from "zod";
4
5
  import { NumberField } from "@/components/ui/number-field";
5
6
  import { cn } from "@/utils/cn";
@@ -85,6 +86,7 @@ const SliderComponent = ({
85
86
  disabled,
86
87
  }: SliderProps): JSX.Element => {
87
88
  const id = useId();
89
+ const { locale } = useLocale();
88
90
 
89
91
  // Hold internal value
90
92
  const [internalValue, setInternalValue] = useState(value);
@@ -138,7 +140,7 @@ const SliderComponent = ({
138
140
  />
139
141
  {showValue && (
140
142
  <div className="text-xs text-muted-foreground min-w-[16px]">
141
- {prettyScientificNumber(valueMap(internalValue))}
143
+ {prettyScientificNumber(valueMap(internalValue), { locale })}
142
144
  </div>
143
145
  )}
144
146
  {includeInput && (
@@ -17,8 +17,10 @@ export type EventHandler = (...args: any[]) => void;
17
17
 
18
18
  class ModelManager {
19
19
  private models = new Map<string, Deferred<Model<any>>>();
20
-
21
- constructor(private timeout = 10_000) {}
20
+ private timeout: number;
21
+ constructor(timeout = 10_000) {
22
+ this.timeout = timeout;
23
+ }
22
24
 
23
25
  get(key: string): Promise<Model<any>> {
24
26
  let deferred = this.models.get(key);
@@ -64,16 +66,25 @@ export class Model<T extends Record<string, any>> implements AnyModel<T> {
64
66
  private ANY_CHANGE_EVENT = "change";
65
67
  private dirtyFields;
66
68
  public static _modelManager: ModelManager = MODEL_MANAGER;
69
+ private data: T;
70
+ private onChange: (value: Partial<T>) => void;
71
+ private sendToWidget: (req: {
72
+ content?: any;
73
+ buffers?: ArrayBuffer[] | ArrayBufferView[];
74
+ }) => Promise<null | undefined>;
67
75
 
68
76
  constructor(
69
- private data: T,
70
- private onChange: (value: Partial<T>) => void,
71
- private sendToWidget: (req: {
77
+ data: T,
78
+ onChange: (value: Partial<T>) => void,
79
+ sendToWidget: (req: {
72
80
  content?: any;
73
81
  buffers?: ArrayBuffer[] | ArrayBufferView[];
74
82
  }) => Promise<null | undefined>,
75
83
  initialDirtyFields: Set<keyof T>,
76
84
  ) {
85
+ this.data = data;
86
+ this.onChange = onChange;
87
+ this.sendToWidget = sendToWidget;
77
88
  this.dirtyFields = new Set(initialDirtyFields);
78
89
  }
79
90
 
@@ -10,11 +10,13 @@ export interface PositionalEdit {
10
10
  value: unknown;
11
11
  }
12
12
 
13
- export enum BulkEdit {
14
- Insert = "insert",
15
- Remove = "remove",
16
- Rename = "rename",
17
- }
13
+ export const BulkEdit = {
14
+ Insert: "insert",
15
+ Remove: "remove",
16
+ Rename: "rename",
17
+ } as const;
18
+
19
+ type BulkEdit = (typeof BulkEdit)[keyof typeof BulkEdit];
18
20
 
19
21
  export interface RowEdit {
20
22
  rowIdx: number;
@@ -3,6 +3,7 @@
3
3
  import type { Schema } from "compassql/build/src/schema";
4
4
  import { BarChartBigIcon } from "lucide-react";
5
5
  import React, { useState } from "react";
6
+ import { useDateFormatter, useNumberFormatter } from "react-aria";
6
7
  import { Button } from "@/components/ui/button";
7
8
  import {
8
9
  Select,
@@ -131,7 +132,9 @@ export const ColumnSummary: React.FC<Props> = ({ schema }) => {
131
132
  {STAT_KEYS.map((key) => (
132
133
  <div key={key} className="flex flex-row gap-2 min-w-[100px]">
133
134
  <span className="font-semibold">{key}</span>
134
- <span>{formatStat(stats?.[key])}</span>
135
+ <span>
136
+ <FormatStat value={stats?.[key]} />
137
+ </span>
135
138
  </div>
136
139
  ))}
137
140
  </div>
@@ -151,24 +154,28 @@ const STAT_KEYS = [
151
154
  "stdev",
152
155
  ];
153
156
 
154
- function formatStat(value: unknown) {
157
+ const FormatStat = ({ value }: { value: unknown }) => {
158
+ // Decimal .2
159
+ const numberFormatter = useNumberFormatter({
160
+ maximumFractionDigits: 2,
161
+ });
162
+
163
+ // Just day, month, year
164
+ const dateFormatter = useDateFormatter({
165
+ year: "numeric",
166
+ month: "short",
167
+ day: "numeric",
168
+ });
169
+
155
170
  if (typeof value === "number") {
156
- // Decimal .2
157
- return new Intl.NumberFormat("en-US", {
158
- maximumFractionDigits: 2,
159
- }).format(value);
171
+ return numberFormatter.format(value);
160
172
  }
161
173
  if (typeof value === "string") {
162
174
  return value;
163
175
  }
164
176
  if (typeof value === "object" && value instanceof Date) {
165
- // Just day, month, year
166
- return new Intl.DateTimeFormat("en-US", {
167
- year: "numeric",
168
- month: "short",
169
- day: "numeric",
170
- }).format(value);
177
+ return dateFormatter.format(value);
171
178
  }
172
179
 
173
180
  return String(value);
174
- }
181
+ };
@@ -57,11 +57,13 @@ export class EventBuffer<T> {
57
57
  private buffer: T[] = [];
58
58
  private isBlocked = false;
59
59
  private timeout: number | null = null;
60
+ private processEvents: () => void;
61
+ private blockDuration: number;
60
62
 
61
- constructor(
62
- private processEvents: () => void,
63
- private blockDuration = 200,
64
- ) {}
63
+ constructor(processEvents: () => void, blockDuration = 200) {
64
+ this.processEvents = processEvents;
65
+ this.blockDuration = blockDuration;
66
+ }
65
67
 
66
68
  add(event: T) {
67
69
  this.buffer.push(event);