@marimo-team/frontend 0.23.1-dev2 → 0.23.1-dev20

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 (122) hide show
  1. package/dist/assets/{CellStatus-CNNGwOIK.js → CellStatus-zTcdYfqx.js} +1 -1
  2. package/dist/assets/{ConnectedDataExplorerComponent-CfU-ThkK.js → ConnectedDataExplorerComponent-B0Mh-Jhz.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-dxChUL02.js → ErrorBoundary-CxTq44MI.js} +1 -1
  4. package/dist/assets/{ImperativeModal-DoGv2BXV.js → ImperativeModal-hsPVDTG-.js} +1 -1
  5. package/dist/assets/{JsonOutput-D_ORGqdz.js → JsonOutput-CavtrueA.js} +1 -1
  6. package/dist/assets/{LazyAnyLanguageCodeMirror-CoYqQxHb.js → LazyAnyLanguageCodeMirror-CsrwfW0n.js} +2 -2
  7. package/dist/assets/{MarimoErrorOutput-Bc9JufDr.js → MarimoErrorOutput-Bmp8DLLo.js} +1 -1
  8. package/dist/assets/{RSPContexts-LWFBF00h.js → RSPContexts-DzigvqmT.js} +1 -1
  9. package/dist/assets/RenderHTML-CM3WMmA8.js +1 -0
  10. package/dist/assets/{add-cell-with-ai-3_AIzd22.js → add-cell-with-ai-_Y6SqxBB.js} +1 -1
  11. package/dist/assets/{add-connection-dialog-DGgtN73u.js → add-connection-dialog-BGZvJkor.js} +1 -1
  12. package/dist/assets/{agent-panel-D7gqlew5.js → agent-panel-BvL9Lu9c.js} +1 -1
  13. package/dist/assets/{ai-model-dropdown-DWOGmhDj.js → ai-model-dropdown-Dyxi3_nW.js} +1 -1
  14. package/dist/assets/{alert-dialog-CXspBRlP.js → alert-dialog-C2mTH3GM.js} +1 -1
  15. package/dist/assets/{any-language-editor-DYgTL8eG.js → any-language-editor-DkEDDsUJ.js} +1 -1
  16. package/dist/assets/{app-config-button-BxCSZCVS.js → app-config-button-BT2Do4RJ.js} +1 -1
  17. package/dist/assets/button-COIw2x9i.js +1 -0
  18. package/dist/assets/{cache-panel-8E_Y5OSb.js → cache-panel-C3V9UubH.js} +1 -1
  19. package/dist/assets/{cell-editor-B2IIBFCB.js → cell-editor-B40o_zx_.js} +1 -1
  20. package/dist/assets/{cell-link-CcAqXeeg.js → cell-link-CRkrHl-y.js} +1 -1
  21. package/dist/assets/{cells-EJo3u4za.js → cells-BqYYXi6G.js} +69 -69
  22. package/dist/assets/{chat-display-DFUo2Riv.js → chat-display-M_nvYuHH.js} +1 -1
  23. package/dist/assets/{chat-panel-Dl4jq1Dp.js → chat-panel-BMOW93uQ.js} +1 -1
  24. package/dist/assets/{chat-ui-CysJeVE6.js → chat-ui-DyeimpVh.js} +1 -1
  25. package/dist/assets/{column-preview-DQBtRWJG.js → column-preview-AfcgbFG_.js} +1 -1
  26. package/dist/assets/{command-DvF_4mQa.js → command-DnzBp4l4.js} +1 -1
  27. package/dist/assets/{command-palette-BCrWwbIt.js → command-palette-BgvdyU3B.js} +1 -1
  28. package/dist/assets/{common-Bty2yo-n.js → common-DeoGL9rK.js} +1 -1
  29. package/dist/assets/{components-Dh-L-jYg.js → components-CDgxb-5o.js} +1 -1
  30. package/dist/assets/{components-B8TZ_vT_.js → components-DKHyHZBv.js} +1 -1
  31. package/dist/assets/{copy-icon-BGs1Pbai.js → copy-icon-Ci08KCdY.js} +1 -1
  32. package/dist/assets/{datasource-DY0N42ZB.js → datasource-COFRe84u.js} +1 -1
  33. package/dist/assets/{dependency-graph-panel-C23HsAdh.js → dependency-graph-panel-BXSe6z1R.js} +1 -1
  34. package/dist/assets/{dialog-BYjetQgE.js → dialog-H-hXtEOq.js} +1 -1
  35. package/dist/assets/{documentation-panel-okcEKCQM.js → documentation-panel-DUPcsi8P.js} +1 -1
  36. package/dist/assets/{download-TSo32ofd.js → download-5XbM3TL_.js} +1 -1
  37. package/dist/assets/edit-page-BDgzn0ig.js +9 -0
  38. package/dist/assets/{error-banner-bXc_9BBZ.js → error-banner-frvr6JXK.js} +1 -1
  39. package/dist/assets/{error-panel-aq2j0jIa.js → error-panel-DQOeSv5-.js} +1 -1
  40. package/dist/assets/{field-DTzXkFLZ.js → field-DaQqUJ5I.js} +1 -1
  41. package/dist/assets/{file-explorer-panel-bcSqGkbZ.js → file-explorer-panel-B67zjs2X.js} +1 -1
  42. package/dist/assets/{file-icons-DBaXCICA.js → file-icons-Bj5YoM7H.js} +1 -1
  43. package/dist/assets/{floating-outline-BTmyhMGv.js → floating-outline-XObNWtN8.js} +1 -1
  44. package/dist/assets/{focus-DXeddo75.js → focus-DzMo6UAI.js} +1 -1
  45. package/dist/assets/{form-30oC5z9y.js → form-BJ6VFU8l.js} +1 -1
  46. package/dist/assets/{gallery-page-XSrY7bw_.js → gallery-page-BDI9wEUZ.js} +1 -1
  47. package/dist/assets/{glide-data-editor-Bd4FOxvW.js → glide-data-editor-QI6B6uKw.js} +1 -1
  48. package/dist/assets/{globals-DQM2RvzM.js → globals-Bu6OEURn.js} +1 -1
  49. package/dist/assets/{home-page-CruHjoHv.js → home-page-BUdd5uTz.js} +1 -1
  50. package/dist/assets/{hooks-DLUrd-jH.js → hooks-DvwShzDb.js} +1 -1
  51. package/dist/assets/{html-to-image-BJiJlwQY.js → html-to-image-DGqJ93hW.js} +1 -1
  52. package/dist/assets/index-C9DyCFTe.js +42 -0
  53. package/dist/assets/index-CKRn_SiB.css +2 -0
  54. package/dist/assets/{input-Bg12i6qY.js → input-DNCT6U6R.js} +1 -1
  55. package/dist/assets/{kiosk-mode-JCcLyeoQ.js → kiosk-mode-DYHoqMaZ.js} +1 -1
  56. package/dist/assets/layout-erv8pLIP.js +9 -0
  57. package/dist/assets/{logs-panel-BzhPrie8.js → logs-panel-CRW4c2IL.js} +1 -1
  58. package/dist/assets/{markdown-renderer-B9RsGqHb.js → markdown-renderer-DNANigO8.js} +1 -1
  59. package/dist/assets/{mermaid-BJFSZcG6.js → mermaid-BPufPrIN.js} +1 -1
  60. package/dist/assets/{name-cell-input-CYsY4A1G.js → name-cell-input-3iKP6YTw.js} +1 -1
  61. package/dist/assets/{outline-panel-BCAWCKi6.js → outline-panel-VIqWcHj6.js} +1 -1
  62. package/dist/assets/{packages-panel-DxS7zji3.js → packages-panel-D_z4ylBE.js} +1 -1
  63. package/dist/assets/panels--5tTbFBo.js +1 -0
  64. package/dist/assets/{process-output-DqiZsqG9.js → process-output-Q6wVr7a-.js} +1 -1
  65. package/dist/assets/{readonly-python-code-D8ITm60r.js → readonly-python-code-CI_b818F.js} +1 -1
  66. package/dist/assets/{run-page-CI2eOA-G.js → run-page-DN26u83D.js} +1 -1
  67. package/dist/assets/{scratchpad-panel-Dwp8-2S1.js → scratchpad-panel-CnaiXtoJ.js} +1 -1
  68. package/dist/assets/{secrets-panel-C6X5jB8Q.js → secrets-panel-DWMqzwkS.js} +1 -1
  69. package/dist/assets/{select--zcABebs.js → select-BwwUWhww.js} +1 -1
  70. package/dist/assets/{session-panel-ryqVmqVd.js → session-panel-C68GBFwH.js} +1 -1
  71. package/dist/assets/{slides-component-DXMG6OXG.js → slides-component-ncUJNz7U.js} +1 -1
  72. package/dist/assets/{snippets-panel--mh2FUXA.js → snippets-panel-BmIdR0lc.js} +1 -1
  73. package/dist/assets/{state-CMxx6hcP.js → state-DAlJ-NbL.js} +1 -1
  74. package/dist/assets/{state-BDrig0S2.js → state-DPomuurt.js} +1 -1
  75. package/dist/assets/{switch-C6xjg01T.js → switch-YkPg_CVc.js} +1 -1
  76. package/dist/assets/{textarea-Cfp3upzK.js → textarea-CS2o3y4W.js} +1 -1
  77. package/dist/assets/{tracing-BExYhl1z.js → tracing-CPDDwzIA.js} +1 -1
  78. package/dist/assets/{tracing-panel-Co5DeX-F.js → tracing-panel-Ku1LapXJ.js} +2 -2
  79. package/dist/assets/{useAddCell-BaTlDxTu.js → useAddCell-B6yUY_RG.js} +1 -1
  80. package/dist/assets/{useBoolean-BvsK1Xcs.js → useBoolean-Dk1Mb_so.js} +1 -1
  81. package/dist/assets/{useCellActionButton-DftkIqUl.js → useCellActionButton-SxeK4dmW.js} +1 -1
  82. package/dist/assets/{useDeleteCell-d6yWnL3H.js → useDeleteCell-DHUjJQJx.js} +1 -1
  83. package/dist/assets/{useDependencyPanelTab-BaVcOBM4.js → useDependencyPanelTab-CflgayoH.js} +1 -1
  84. package/dist/assets/{useNotebookActions-D8Dm93y8.js → useNotebookActions-Ch1o32Jw.js} +1 -1
  85. package/dist/assets/{useRunCells-d2edY6Tu.js → useRunCells-DFYAOTWd.js} +1 -1
  86. package/dist/assets/{useSplitCell-DOiFyMgH.js → useSplitCell-Bh-NZsBl.js} +1 -1
  87. package/dist/assets/{vega-component-CiVPyAwP.js → vega-component-DtSTT5wO.js} +1 -1
  88. package/dist/assets/{write-secret-modal-CHfFN0H8.js → write-secret-modal-CInxHVWJ.js} +1 -1
  89. package/dist/index.html +39 -39
  90. package/package.json +1 -1
  91. package/src/components/editor/renderers/slides-layout/slides-layout.tsx +50 -44
  92. package/src/components/slides/__tests__/minimap.test.ts +402 -0
  93. package/src/components/slides/minimap.tsx +534 -0
  94. package/src/components/slides/slide.tsx +29 -0
  95. package/src/components/slides/slides-component.tsx +16 -1
  96. package/src/components/ui/button.tsx +1 -2
  97. package/src/core/cells/__tests__/cells.test.ts +105 -1
  98. package/src/core/cells/cells.ts +43 -0
  99. package/src/core/cells/document-changes.ts +2 -1
  100. package/src/plugins/core/RenderHTML.tsx +9 -0
  101. package/src/plugins/core/__test__/RenderHTML.test.ts +27 -0
  102. package/src/plugins/core/registerReactComponent.tsx +11 -8
  103. package/src/plugins/impl/ButtonPlugin.tsx +4 -6
  104. package/src/plugins/impl/CodeEditorPlugin.tsx +15 -18
  105. package/src/plugins/impl/DataEditorPlugin.tsx +8 -14
  106. package/src/plugins/impl/DataTablePlugin.tsx +1 -6
  107. package/src/plugins/impl/FileUploadPlugin.tsx +39 -43
  108. package/src/plugins/impl/FormPlugin.tsx +2 -6
  109. package/src/plugins/impl/chat/ChatPlugin.tsx +17 -20
  110. package/src/plugins/impl/data-explorer/DataExplorerPlugin.tsx +5 -8
  111. package/src/plugins/impl/matplotlib/matplotlib-renderer.ts +38 -14
  112. package/src/plugins/impl/vega/VegaPlugin.tsx +5 -8
  113. package/src/plugins/layout/NavigationMenuPlugin.tsx +2 -6
  114. package/src/utils/__tests__/events.test.ts +223 -0
  115. package/src/utils/events.ts +28 -14
  116. package/dist/assets/RenderHTML-0dk6-mYI.js +0 -1
  117. package/dist/assets/button-BKVLeSTX.js +0 -1
  118. package/dist/assets/edit-page-fl9KJOcI.js +0 -13
  119. package/dist/assets/index-DTBKKey_.js +0 -42
  120. package/dist/assets/index-qO0a4zuT.css +0 -2
  121. package/dist/assets/layout-cq-nxDfO.js +0 -5
  122. package/dist/assets/panels-B5BFPT3k.js +0 -1
@@ -297,13 +297,7 @@ export class MatplotlibRenderer {
297
297
  // Create canvas
298
298
  const canvas = document.createElement("canvas");
299
299
  canvas.className = "block cursor-crosshair";
300
- const dpr = globalThis.devicePixelRatio ?? 1;
301
- canvas.width = this.#state.width * dpr;
302
- canvas.height = this.#state.height * dpr;
303
- canvas.style.width = `${this.#state.width}px`;
304
- canvas.style.maxWidth = "100%";
305
- canvas.style.height = "auto";
306
- canvas.style.aspectRatio = `${this.#state.width} / ${this.#state.height}`;
300
+ this.#syncCanvasSize(canvas);
307
301
  canvas.style.touchAction = "none";
308
302
  container.append(canvas);
309
303
  this.#canvas = canvas;
@@ -322,6 +316,10 @@ export class MatplotlibRenderer {
322
316
  signal: options.signal,
323
317
  });
324
318
 
319
+ // Watch for devicePixelRatio changes (e.g. browser zoom, moving between
320
+ // displays). matchMedia fires exactly once per DPR transition.
321
+ this.#watchDevicePixelRatio(options.signal);
322
+
325
323
  // Clean up on abort
326
324
  options.signal.addEventListener("abort", () => {
327
325
  cancelAnimationFrame(this.#rafId);
@@ -333,6 +331,38 @@ export class MatplotlibRenderer {
333
331
  this.#restoreSelection(this.#state.value);
334
332
  }
335
333
 
334
+ /** Set the canvas buffer + CSS size to match current logical size and DPR. */
335
+ #syncCanvasSize(canvas: HTMLCanvasElement = this.#canvas): void {
336
+ const dpr = globalThis.devicePixelRatio ?? 1;
337
+ const { width, height } = this.#state;
338
+ canvas.width = width * dpr;
339
+ canvas.height = height * dpr;
340
+ canvas.style.width = `${width}px`;
341
+ canvas.style.maxWidth = "100%";
342
+ canvas.style.height = "auto";
343
+ canvas.style.aspectRatio = `${width} / ${height}`;
344
+ }
345
+
346
+ /**
347
+ * Observe devicePixelRatio changes via matchMedia. Each listener fires once
348
+ * per transition, so we re-register after every change.
349
+ */
350
+ #watchDevicePixelRatio(signal: AbortSignal): void {
351
+ if (signal.aborted) {
352
+ return;
353
+ }
354
+ const mq = matchMedia(
355
+ `(resolution: ${globalThis.devicePixelRatio ?? 1}dppx)`,
356
+ );
357
+ const onChange = () => {
358
+ this.#syncCanvasSize();
359
+ this.#drawCanvas();
360
+ // Re-register for the next DPR transition
361
+ this.#watchDevicePixelRatio(signal);
362
+ };
363
+ mq.addEventListener("change", onChange, { once: true, signal });
364
+ }
365
+
336
366
  update(state: MatplotlibState): void {
337
367
  const prev = this.#state;
338
368
  this.#state = state;
@@ -341,13 +371,7 @@ export class MatplotlibRenderer {
341
371
 
342
372
  // Update canvas dimensions if changed
343
373
  if (state.width !== prev.width || state.height !== prev.height) {
344
- const dpr = globalThis.devicePixelRatio ?? 1;
345
- this.#canvas.width = state.width * dpr;
346
- this.#canvas.height = state.height * dpr;
347
- this.#canvas.style.width = `${state.width}px`;
348
- this.#canvas.style.maxWidth = "100%";
349
- this.#canvas.style.height = "auto";
350
- this.#canvas.style.aspectRatio = `${state.width} / ${state.height}`;
374
+ this.#syncCanvasSize();
351
375
  needsRedraw = true;
352
376
  }
353
377
 
@@ -8,7 +8,6 @@ import type { Data, VegaComponentState } from "./vega-component";
8
8
 
9
9
  import "./vega.css";
10
10
  import React, { type JSX } from "react";
11
- import { TooltipProvider } from "@/components/ui/tooltip";
12
11
 
13
12
  const LazyVegaComponent = React.lazy(() => import("./vega-component"));
14
13
 
@@ -29,13 +28,11 @@ export class VegaPlugin implements IPlugin<VegaComponentState, Data> {
29
28
 
30
29
  render(props: IPluginProps<VegaComponentState, Data>): JSX.Element {
31
30
  return (
32
- <TooltipProvider>
33
- <LazyVegaComponent
34
- value={props.value}
35
- setValue={props.setValue}
36
- {...props.data}
37
- />
38
- </TooltipProvider>
31
+ <LazyVegaComponent
32
+ value={props.value}
33
+ setValue={props.setValue}
34
+ {...props.data}
35
+ />
39
36
  );
40
37
  }
41
38
  }
@@ -11,7 +11,7 @@ import {
11
11
  NavigationMenuTrigger,
12
12
  navigationMenuTriggerStyle,
13
13
  } from "@/components/ui/navigation";
14
- import { Tooltip, TooltipProvider } from "@/components/ui/tooltip";
14
+ import { Tooltip } from "@/components/ui/tooltip";
15
15
  import { renderHTML } from "@/plugins/core/RenderHTML";
16
16
  import { cn } from "@/utils/cn";
17
17
  import { appendQueryParams } from "@/utils/urls";
@@ -67,11 +67,7 @@ export class NavigationMenuPlugin implements IStatelessPlugin<Data> {
67
67
  });
68
68
 
69
69
  render(props: IStatelessPluginProps<Data>): JSX.Element {
70
- return (
71
- <TooltipProvider>
72
- <NavMenuComponent {...props.data} />
73
- </TooltipProvider>
74
- );
70
+ return <NavMenuComponent {...props.data} />;
75
71
  }
76
72
  }
77
73
 
@@ -0,0 +1,223 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { describe, expect, test } from "vitest";
4
+ import { Events } from "../events";
5
+
6
+ /**
7
+ * Create a minimal fake event with just a target (no composedPath).
8
+ * Simulates synthetic events from libraries like React Aria.
9
+ */
10
+ function fakeEvent(target: EventTarget): Pick<Event, "target"> {
11
+ return { target };
12
+ }
13
+
14
+ /**
15
+ * Create a fake native-like KeyboardEvent with composedPath() returning
16
+ * a different element than target — the key scenario for shadow DOM
17
+ * retargeting where target is the shadow host but composedPath()[0]
18
+ * is the real focused element inside the shadow root.
19
+ */
20
+ function fakeNativeEvent(
21
+ retargetedHost: EventTarget,
22
+ realTarget: EventTarget,
23
+ ): KeyboardEvent {
24
+ const event = new KeyboardEvent("keydown");
25
+ Object.defineProperty(event, "target", { value: retargetedHost });
26
+ Object.defineProperty(event, "composedPath", {
27
+ value: () => [realTarget, retargetedHost, document.body, document],
28
+ });
29
+ return event;
30
+ }
31
+
32
+ describe("Events.composedTarget", () => {
33
+ test("returns composedPath()[0] for native events", () => {
34
+ const input = document.createElement("input");
35
+ const host = document.createElement("div");
36
+ const event = fakeNativeEvent(host, input);
37
+
38
+ expect(Events.composedTarget(event)).toBe(input);
39
+ });
40
+
41
+ test("falls back to e.target when composedPath is absent", () => {
42
+ const div = document.createElement("div");
43
+ const event = fakeEvent(div);
44
+
45
+ expect(Events.composedTarget(event)).toBe(div);
46
+ });
47
+
48
+ test("falls back to e.target when composedPath returns empty array", () => {
49
+ const div = document.createElement("div");
50
+ const event = new KeyboardEvent("keydown");
51
+ Object.defineProperty(event, "target", { value: div });
52
+ Object.defineProperty(event, "composedPath", { value: () => [] });
53
+
54
+ expect(Events.composedTarget(event)).toBe(div);
55
+ });
56
+ });
57
+
58
+ describe("Events.shouldIgnoreKeyboardEvent", () => {
59
+ test("ignores events from <input>", () => {
60
+ const input = document.createElement("input");
61
+ const event = fakeNativeEvent(input, input);
62
+ expect(Events.shouldIgnoreKeyboardEvent(event)).toBe(true);
63
+ });
64
+
65
+ test("ignores events from <textarea>", () => {
66
+ const textarea = document.createElement("textarea");
67
+ const event = fakeNativeEvent(textarea, textarea);
68
+ expect(Events.shouldIgnoreKeyboardEvent(event)).toBe(true);
69
+ });
70
+
71
+ test("ignores events from <select>", () => {
72
+ const select = document.createElement("select");
73
+ const event = fakeNativeEvent(select, select);
74
+ expect(Events.shouldIgnoreKeyboardEvent(event)).toBe(true);
75
+ });
76
+
77
+ test("ignores events from <button>", () => {
78
+ const button = document.createElement("button");
79
+ const event = fakeNativeEvent(button, button);
80
+ expect(Events.shouldIgnoreKeyboardEvent(event)).toBe(true);
81
+ });
82
+
83
+ test("ignores events from contentEditable elements", () => {
84
+ const div = document.createElement("div");
85
+ div.setAttribute("contenteditable", "true");
86
+ document.body.append(div);
87
+
88
+ const event = fakeNativeEvent(div, div);
89
+ expect(Events.shouldIgnoreKeyboardEvent(event)).toBe(true);
90
+
91
+ div.remove();
92
+ });
93
+
94
+ test("ignores events from elements with role='textbox'", () => {
95
+ const container = document.createElement("div");
96
+ container.setAttribute("role", "textbox");
97
+ const child = document.createElement("span");
98
+ container.append(child);
99
+ document.body.append(container);
100
+
101
+ const event = fakeNativeEvent(child, child);
102
+ expect(Events.shouldIgnoreKeyboardEvent(event)).toBe(true);
103
+
104
+ container.remove();
105
+ });
106
+
107
+ test("ignores events from inside .cm-editor", () => {
108
+ const editor = document.createElement("div");
109
+ editor.className = "cm-editor";
110
+ const line = document.createElement("div");
111
+ editor.append(line);
112
+ document.body.append(editor);
113
+
114
+ const event = fakeNativeEvent(line, line);
115
+ expect(Events.shouldIgnoreKeyboardEvent(event)).toBe(true);
116
+
117
+ editor.remove();
118
+ });
119
+
120
+ test("does NOT ignore events from a plain <div>", () => {
121
+ const div = document.createElement("div");
122
+ const event = fakeNativeEvent(div, div);
123
+ expect(Events.shouldIgnoreKeyboardEvent(event)).toBe(false);
124
+ });
125
+
126
+ describe("shadow DOM retargeting (#4230)", () => {
127
+ test("ignores keydown when real target inside shadow root is <input>", () => {
128
+ const host = document.createElement("marimo-text");
129
+ const input = document.createElement("input");
130
+ // event.target is the shadow host, composedPath()[0] is the real input
131
+ const event = fakeNativeEvent(host, input);
132
+ expect(Events.shouldIgnoreKeyboardEvent(event)).toBe(true);
133
+ });
134
+
135
+ test("ignores keydown when real target inside shadow root is <textarea>", () => {
136
+ const host = document.createElement("marimo-text-area");
137
+ const textarea = document.createElement("textarea");
138
+ const event = fakeNativeEvent(host, textarea);
139
+ expect(Events.shouldIgnoreKeyboardEvent(event)).toBe(true);
140
+ });
141
+
142
+ test("ignores keydown when real target inside shadow root is <select>", () => {
143
+ const host = document.createElement("marimo-dropdown");
144
+ const select = document.createElement("select");
145
+ const event = fakeNativeEvent(host, select);
146
+ expect(Events.shouldIgnoreKeyboardEvent(event)).toBe(true);
147
+ });
148
+
149
+ test("does NOT ignore when shadow DOM real target is a plain <div>", () => {
150
+ const host = document.createElement("marimo-output");
151
+ const div = document.createElement("div");
152
+ const event = fakeNativeEvent(host, div);
153
+ expect(Events.shouldIgnoreKeyboardEvent(event)).toBe(false);
154
+ });
155
+ });
156
+
157
+ test("does NOT ignore events from non-marimo custom elements", () => {
158
+ const el = document.createElement("sl-input");
159
+ const event = fakeNativeEvent(el, el);
160
+ expect(Events.shouldIgnoreKeyboardEvent(event)).toBe(false);
161
+ });
162
+ });
163
+
164
+ describe("Events.fromInput", () => {
165
+ test("returns true for <input>", () => {
166
+ const input = document.createElement("input");
167
+ expect(Events.fromInput(fakeEvent(input))).toBe(true);
168
+ });
169
+
170
+ test("returns true for <textarea>", () => {
171
+ const textarea = document.createElement("textarea");
172
+ expect(Events.fromInput(fakeEvent(textarea))).toBe(true);
173
+ });
174
+
175
+ test("returns true for marimo custom elements", () => {
176
+ const el = document.createElement("marimo-slider");
177
+ expect(Events.fromInput(fakeEvent(el))).toBe(true);
178
+ });
179
+
180
+ // jsdom does not implement isContentEditable, so this is tested
181
+ // via shouldIgnoreKeyboardEvent which has a closest() fallback.
182
+ test.skip("returns true for contentEditable", () => {
183
+ const div = document.createElement("div");
184
+ div.setAttribute("contenteditable", "true");
185
+ document.body.append(div);
186
+
187
+ expect(Events.fromInput(fakeEvent(div))).toBe(true);
188
+
189
+ div.remove();
190
+ });
191
+
192
+ test("returns false for plain <div>", () => {
193
+ const div = document.createElement("div");
194
+ expect(Events.fromInput(fakeEvent(div))).toBe(false);
195
+ });
196
+
197
+ test("uses composedPath when available (shadow DOM)", () => {
198
+ const host = document.createElement("div");
199
+ const input = document.createElement("input");
200
+ const event = fakeNativeEvent(host, input);
201
+ expect(Events.fromInput(event)).toBe(true);
202
+ });
203
+ });
204
+
205
+ describe("Events.fromCodeMirror", () => {
206
+ test("returns true when target is inside .cm-editor", () => {
207
+ const editor = document.createElement("div");
208
+ editor.className = "cm-editor";
209
+ const line = document.createElement("div");
210
+ editor.append(line);
211
+ document.body.append(editor);
212
+
213
+ expect(Events.fromCodeMirror(line)).toBe(true);
214
+ editor.remove();
215
+ });
216
+
217
+ test("returns false when target is not inside .cm-editor", () => {
218
+ const div = document.createElement("div");
219
+ document.body.append(div);
220
+ expect(Events.fromCodeMirror(div)).toBe(false);
221
+ div.remove();
222
+ });
223
+ });
@@ -37,21 +37,20 @@ export const Events = {
37
37
  * Returns true if the event is coming from a text input
38
38
  */
39
39
  fromInput: (e: Pick<KeyboardEvent, "target">) => {
40
- const target = e.target as HTMLElement;
40
+ const target = Events.composedTarget(e);
41
41
  return (
42
42
  target.tagName === "INPUT" ||
43
43
  target.tagName === "TEXTAREA" ||
44
44
  target.tagName.startsWith("MARIMO") ||
45
45
  target.isContentEditable ||
46
- Events.fromCodeMirror(e)
46
+ Events.fromCodeMirror(target)
47
47
  );
48
48
  },
49
49
 
50
50
  /**
51
51
  * Returns true if the event is coming from a code editor.
52
52
  */
53
- fromCodeMirror: (e: Pick<KeyboardEvent, "target">) => {
54
- const target = e.target as HTMLElement;
53
+ fromCodeMirror: (target: HTMLElement) => {
55
54
  return target.closest(".cm-editor") !== null;
56
55
  },
57
56
 
@@ -60,20 +59,35 @@ export const Events = {
60
59
  * form element or code editor.
61
60
  */
62
61
  shouldIgnoreKeyboardEvent(e: KeyboardEvent) {
63
- // Check for common form elements where keyboard shortcuts should be ignored
62
+ const target = Events.composedTarget(e);
64
63
  return (
65
- e.target instanceof HTMLInputElement ||
66
- e.target instanceof HTMLTextAreaElement ||
67
- e.target instanceof HTMLSelectElement ||
68
- (e.target instanceof HTMLElement &&
69
- (e.target.isContentEditable ||
70
- e.target.tagName === "BUTTON" ||
71
- e.target.closest("[role='textbox']") !== null ||
72
- e.target.closest("[contenteditable='true']") !== null ||
73
- e.target.closest(".cm-editor") !== null)) // Add check for CodeMirror editor
64
+ target instanceof HTMLInputElement ||
65
+ target instanceof HTMLTextAreaElement ||
66
+ target instanceof HTMLSelectElement ||
67
+ (target instanceof HTMLElement &&
68
+ (target.isContentEditable ||
69
+ target.tagName === "BUTTON" ||
70
+ target.closest("[role='textbox']") !== null ||
71
+ target.closest("[contenteditable='true']") !== null ||
72
+ Events.fromCodeMirror(target)))
74
73
  );
75
74
  },
76
75
 
76
+ /**
77
+ * Resolve the real event target, piercing shadow DOM retargeting.
78
+ * Falls back to e.target for synthetic events (e.g. React Aria)
79
+ * that don't expose composedPath.
80
+ *
81
+ * Without this, e.target is the shadow host (e.g. <marimo-text>) rather than the
82
+ * real <input> inside it, so instanceof checks fail. (#4230)
83
+ */
84
+ composedTarget(e: Pick<Event, "target">): HTMLElement {
85
+ if ("composedPath" in e && typeof e.composedPath === "function") {
86
+ return (e.composedPath()[0] ?? e.target) as HTMLElement;
87
+ }
88
+ return e.target as HTMLElement;
89
+ },
90
+
77
91
  hasModifier: (
78
92
  e: Pick<KeyboardEvent, "ctrlKey" | "metaKey" | "altKey" | "shiftKey">,
79
93
  ) => {
@@ -1 +0,0 @@
1
- import{s as v}from"./chunk-LvLJmgfZ.js";import{i as j,p as y,u as w}from"./useEvent-D91BmmQi.js";import{t as R}from"./react-Bj1aDYRI.js";import{bn as S,gn as _,ii as c,it as H,m as I,ri as M,vt as N}from"./cells-EJo3u4za.js";import{t as E}from"./compiler-runtime-B3qBwwSJ.js";import{_ as T}from"./useEventListener-DGjKht0c.js";import{o as z}from"./utils-8btzWeZg.js";import{t as C}from"./jsx-runtime-Blw4afVn.js";import{t as F}from"./tooltip-DmqhBBs6.js";import{t as L}from"./copy-icon-BGs1Pbai.js";import{t as V}from"./usePress-BXMIcLWP.js";import{n as k}from"./useDebounce-LVL1r3-M.js";import{t as q}from"./useRunCells-d2edY6Tu.js";var W=E(),m=v(R(),1),o=v(C(),1);const A=r=>{let t=(0,W.c)(16),e,n,i;t[0]===r?(e=t[1],n=t[2],i=t[3]):({href:n,children:e,...i}=r,t[0]=r,t[1]=e,t[2]=n,t[3]=i);let a=(0,m.useRef)(null),s;t[4]===n?s=t[5]:(s=()=>{let u=new URL(globalThis.location.href);u.hash=n,globalThis.history.pushState({},"",u.toString()),globalThis.dispatchEvent(new HashChangeEvent("hashchange"));let x=n.slice(1),g=document.getElementById(x);g&&g.scrollIntoView({behavior:"smooth",block:"start"})},t[4]=n,t[5]=s);let l=s,f;t[6]===l?f=t[7]:(f={onPress:()=>{l()}},t[6]=l,t[7]=f);let{pressProps:h}=V(f),d;t[8]===l?d=t[9]:(d=u=>{u.preventDefault(),l()},t[8]=l,t[9]=d);let b=d,p;return t[10]!==e||t[11]!==b||t[12]!==n||t[13]!==h||t[14]!==i?(p=(0,o.jsx)("a",{ref:a,href:n,...h,onClick:b,...i,children:e}),t[10]=e,t[11]=b,t[12]=n,t[13]=h,t[14]=i,t[15]=p):p=t[15],p};async function D(r){let t=I().inOrderIds.at(0);if(t)try{let e=await _.request({document:r,cellId:t});if(!e||e.options.length===0)return;let n=r.split(".").pop()??r,i=e.options[0],a=e.options.find(s=>s.name===n)??i;a!=null&&a.completion_info&&j.set(S,{documentation:a.completion_info})}catch(e){T.debug(`Doc lookup failed for "${r}"`,e)}}var O=E();const P=r=>{let t=(0,O.c)(8),{qualifiedName:e,children:n}=r,i;t[0]===e?i=t[1]:(i=()=>{D(e)},t[0]=e,t[1]=i);let a=k(i,100),s;t[2]===a?s=t[3]:(s=()=>a.cancel(),t[2]=a,t[3]=s);let l;return t[4]!==n||t[5]!==a||t[6]!==s?(l=(0,o.jsx)("span",{onMouseEnter:a,onMouseLeave:s,children:n}),t[4]=n,t[5]=a,t[6]=s,t[7]=l):l=t[7],l};var $=y(r=>{let t=r(q),e=r(z);if(t||e)return!1;let n=!0;try{n=H()==="read"}catch{return!0}return!n});function B(){return w($)}var U=E(),Z=r=>{if(r instanceof c.Element&&!/^[A-Za-z][\w-]*$/.test(r.name))return m.createElement(m.Fragment)},G=(r,t)=>{if(t instanceof c.Element&&t.name==="body"){if((0,m.isValidElement)(r)&&"props"in r){let e=r.props.children;return(0,o.jsx)(o.Fragment,{children:e})}return}},J=(r,t)=>{if(t instanceof c.Element&&t.name==="html"){if((0,m.isValidElement)(r)&&"props"in r){let e=r.props.children;return(0,o.jsx)(o.Fragment,{children:e})}return}},K=r=>{if(r instanceof c.Element&&r.attribs&&r.name==="iframe"){let t=document.createElement("iframe");return Object.entries(r.attribs).forEach(([e,n])=>{e.startsWith('"')&&e.endsWith('"')&&(e=e.slice(1,-1)),t.setAttribute(e,n)}),(0,o.jsx)("div",{dangerouslySetInnerHTML:{__html:t.outerHTML}})}},Q=r=>{if(r instanceof c.Element&&r.name==="script"){let t=r.attribs.src;if(!t)return;if(!document.querySelector(`script[src="${t}"]`)){let e=document.createElement("script");e.src=t,document.head.append(e)}return(0,o.jsx)(o.Fragment,{})}},X=(r,t)=>{if(t instanceof c.Element&&t.name==="a"){let e=t.attribs.href;if(e!=null&&e.startsWith("#")&&!e.startsWith("#code/")){let n=null;return(0,m.isValidElement)(r)&&"props"in r&&(n=r.props.children),(0,o.jsx)(A,{href:e,...t.attribs,children:n})}}},Y=(r,t,e)=>{var n,i;if(t instanceof c.Element&&t.name==="div"&&((i=(n=t.attribs)==null?void 0:n.class)!=null&&i.includes("codehilite")))return(0,o.jsx)(rt,{children:r},e)},tt=(r,t)=>{var e;if(t instanceof c.Element&&((e=t.attribs)!=null&&e["data-marimo-doc"])){let n=t.attribs["data-marimo-doc"];return(0,o.jsx)(P,{qualifiedName:n,children:r})}},et=(r,t)=>{var e;if(t instanceof c.Element&&((e=t.attribs)!=null&&e["data-tooltip"])){let n=t.attribs["data-tooltip"];return(0,o.jsx)(F,{content:n,children:r})}},rt=r=>{let t=(0,U.c)(3),{children:e}=r,n=(0,m.useRef)(null),i;t[0]===Symbol.for("react.memo_cache_sentinel")?(i=(0,o.jsx)("div",{className:"absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity",children:(0,o.jsx)(L,{tooltip:!1,className:"p-1",value:()=>{var l;let s=(l=n.current)==null?void 0:l.firstChild;return s&&s.textContent||""}})}),t[0]=i):i=t[0];let a;return t[1]===e?a=t[2]:(a=(0,o.jsxs)("div",{className:"relative group codehilite-wrapper",ref:n,children:[e,i]}),t[1]=e,t[2]=a),a};const nt=({html:r,additionalReplacements:t=[],alwaysSanitizeHtml:e=!0})=>(0,o.jsx)(it,{html:r,alwaysSanitizeHtml:e,additionalReplacements:t});var it=({html:r,additionalReplacements:t=[],alwaysSanitizeHtml:e})=>{let n=B();return at({html:(0,m.useMemo)(()=>e||n?N(r):r,[r,e,n]),additionalReplacements:t})};function at({html:r,additionalReplacements:t=[]}){let e=[Z,K,Q,...t],n=[Y,X,tt,et,G,J];return M(r,{replace:(i,a)=>{for(let s of e){let l=s(i,a);if(l)return l}return i},transform:(i,a,s)=>{for(let l of n){let f=l(i,a,s);if(f)return f}return i}})}export{nt as t};
@@ -1 +0,0 @@
1
- import{s as p}from"./chunk-LvLJmgfZ.js";import{t as T}from"./react-Bj1aDYRI.js";import{t as M}from"./compiler-runtime-B3qBwwSJ.js";import{h as N,n as K,t as C}from"./useEventListener-DGjKht0c.js";import{n as I,t as o}from"./cn-DYvqRARy.js";import{t as H}from"./jsx-runtime-Blw4afVn.js";const m={stopPropagation:e=>t=>{t.stopPropagation(),e&&e(t)},onEnter:e=>t=>{t.key==="Enter"&&e&&e(t)},preventFocus:e=>{e.preventDefault()},fromInput:e=>{let t=e.target;return t.tagName==="INPUT"||t.tagName==="TEXTAREA"||t.tagName.startsWith("MARIMO")||t.isContentEditable||m.fromCodeMirror(e)},fromCodeMirror:e=>e.target.closest(".cm-editor")!==null,shouldIgnoreKeyboardEvent(e){return e.target instanceof HTMLInputElement||e.target instanceof HTMLTextAreaElement||e.target instanceof HTMLSelectElement||e.target instanceof HTMLElement&&(e.target.isContentEditable||e.target.tagName==="BUTTON"||e.target.closest("[role='textbox']")!==null||e.target.closest("[contenteditable='true']")!==null||e.target.closest(".cm-editor")!==null)},hasModifier:e=>e.ctrlKey||e.metaKey||e.altKey||e.shiftKey,isMetaOrCtrl:e=>e.metaKey||e.ctrlKey};var S=M(),v=p(T(),1),z=p(H(),1),a="active:shadow-none",x=I(o("disabled:opacity-50 disabled:pointer-events-none","inline-flex items-center justify-center rounded-md text-sm font-medium focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 ring-offset-background"),{variants:{variant:{default:o("bg-primary text-primary-foreground hover:bg-primary/90 shadow-xs border border-primary",a),destructive:o("border shadow-xs","bg-(--red-9) hover:bg-(--red-10) dark:bg-(--red-6) dark:hover:bg-(--red-7)","text-(--red-1) dark:text-(--red-12)","border-(--red-11)",a),success:o("border shadow-xs","bg-(--grass-9) hover:bg-(--grass-10) dark:bg-(--grass-6) dark:hover:bg-(--grass-7)","text-(--grass-1) dark:text-(--grass-12)","border-(--grass-11)",a),warn:o("border shadow-xs","bg-(--yellow-9) hover:bg-(--yellow-10) dark:bg-(--yellow-6) dark:hover:bg-(--yellow-7)","text-(--yellow-12)","border-(--yellow-11)",a),action:o("bg-action text-action-foreground shadow-xs","hover:bg-action-hover border border-action",a),outline:o("border border-slate-300 shadow-xs","hover:bg-accent hover:text-accent-foreground","hover:border-primary","aria-selected:text-accent-foreground aria-selected:border-primary",a),secondary:o("bg-secondary text-secondary-foreground hover:bg-secondary/80","border border-input shadow-xs",a),text:o("opacity-80 hover:opacity-100","active:opacity-100"),ghost:o("border border-transparent","hover:bg-accent hover:text-accent-foreground hover:shadow-xs",a,"active:text-accent-foreground"),link:"underline-offset-4 hover:underline text-link",linkDestructive:"underline-offset-4 hover:underline text-destructive underline-destructive",outlineDestructive:"border border-destructive text-destructive hover:bg-destructive/10"},size:{default:"h-10 py-2 px-4",xs:"h-7 px-2 rounded-md text-xs",sm:"h-9 px-3 rounded-md",lg:"h-11 px-8 rounded-md",icon:"h-6 w-6 mb-0"},disabled:{true:"opacity-50 pointer-events-none"}},defaultVariants:{variant:"default",size:"sm"}}),y=v.forwardRef((e,t)=>{let r=(0,S.c)(7),{className:f,variant:w,size:k,asChild:h,keyboardShortcut:n,...d}=e,E=h===void 0?!1:h,s=v.useRef(null),i;r[0]===Symbol.for("react.memo_cache_sentinel")?(i=()=>s.current,r[0]=i):i=r[0],v.useImperativeHandle(t,i);let l;r[1]===n?l=r[2]:(l=u=>{n&&(m.shouldIgnoreKeyboardEvent(u)||N(n)(u)&&(u.preventDefault(),u.stopPropagation(),s!=null&&s.current&&!s.current.disabled&&s.current.click()))},r[1]=n,r[2]=l),C(document,"keydown",l);let g=E?K:"button",b=o(x({variant:w,size:k,className:f,disabled:d.disabled}),f),c;return r[3]!==g||r[4]!==d||r[5]!==b?(c=(0,z.jsx)(g,{className:b,ref:s,...d}),r[3]=g,r[4]=d,r[5]=b,r[6]=c):c=r[6],c});y.displayName="Button";export{x as n,m as r,y as t};