@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.
- package/dist/assets/{CellStatus-CNNGwOIK.js → CellStatus-zTcdYfqx.js} +1 -1
- package/dist/assets/{ConnectedDataExplorerComponent-CfU-ThkK.js → ConnectedDataExplorerComponent-B0Mh-Jhz.js} +1 -1
- package/dist/assets/{ErrorBoundary-dxChUL02.js → ErrorBoundary-CxTq44MI.js} +1 -1
- package/dist/assets/{ImperativeModal-DoGv2BXV.js → ImperativeModal-hsPVDTG-.js} +1 -1
- package/dist/assets/{JsonOutput-D_ORGqdz.js → JsonOutput-CavtrueA.js} +1 -1
- package/dist/assets/{LazyAnyLanguageCodeMirror-CoYqQxHb.js → LazyAnyLanguageCodeMirror-CsrwfW0n.js} +2 -2
- package/dist/assets/{MarimoErrorOutput-Bc9JufDr.js → MarimoErrorOutput-Bmp8DLLo.js} +1 -1
- package/dist/assets/{RSPContexts-LWFBF00h.js → RSPContexts-DzigvqmT.js} +1 -1
- package/dist/assets/RenderHTML-CM3WMmA8.js +1 -0
- package/dist/assets/{add-cell-with-ai-3_AIzd22.js → add-cell-with-ai-_Y6SqxBB.js} +1 -1
- package/dist/assets/{add-connection-dialog-DGgtN73u.js → add-connection-dialog-BGZvJkor.js} +1 -1
- package/dist/assets/{agent-panel-D7gqlew5.js → agent-panel-BvL9Lu9c.js} +1 -1
- package/dist/assets/{ai-model-dropdown-DWOGmhDj.js → ai-model-dropdown-Dyxi3_nW.js} +1 -1
- package/dist/assets/{alert-dialog-CXspBRlP.js → alert-dialog-C2mTH3GM.js} +1 -1
- package/dist/assets/{any-language-editor-DYgTL8eG.js → any-language-editor-DkEDDsUJ.js} +1 -1
- package/dist/assets/{app-config-button-BxCSZCVS.js → app-config-button-BT2Do4RJ.js} +1 -1
- package/dist/assets/button-COIw2x9i.js +1 -0
- package/dist/assets/{cache-panel-8E_Y5OSb.js → cache-panel-C3V9UubH.js} +1 -1
- package/dist/assets/{cell-editor-B2IIBFCB.js → cell-editor-B40o_zx_.js} +1 -1
- package/dist/assets/{cell-link-CcAqXeeg.js → cell-link-CRkrHl-y.js} +1 -1
- package/dist/assets/{cells-EJo3u4za.js → cells-BqYYXi6G.js} +69 -69
- package/dist/assets/{chat-display-DFUo2Riv.js → chat-display-M_nvYuHH.js} +1 -1
- package/dist/assets/{chat-panel-Dl4jq1Dp.js → chat-panel-BMOW93uQ.js} +1 -1
- package/dist/assets/{chat-ui-CysJeVE6.js → chat-ui-DyeimpVh.js} +1 -1
- package/dist/assets/{column-preview-DQBtRWJG.js → column-preview-AfcgbFG_.js} +1 -1
- package/dist/assets/{command-DvF_4mQa.js → command-DnzBp4l4.js} +1 -1
- package/dist/assets/{command-palette-BCrWwbIt.js → command-palette-BgvdyU3B.js} +1 -1
- package/dist/assets/{common-Bty2yo-n.js → common-DeoGL9rK.js} +1 -1
- package/dist/assets/{components-Dh-L-jYg.js → components-CDgxb-5o.js} +1 -1
- package/dist/assets/{components-B8TZ_vT_.js → components-DKHyHZBv.js} +1 -1
- package/dist/assets/{copy-icon-BGs1Pbai.js → copy-icon-Ci08KCdY.js} +1 -1
- package/dist/assets/{datasource-DY0N42ZB.js → datasource-COFRe84u.js} +1 -1
- package/dist/assets/{dependency-graph-panel-C23HsAdh.js → dependency-graph-panel-BXSe6z1R.js} +1 -1
- package/dist/assets/{dialog-BYjetQgE.js → dialog-H-hXtEOq.js} +1 -1
- package/dist/assets/{documentation-panel-okcEKCQM.js → documentation-panel-DUPcsi8P.js} +1 -1
- package/dist/assets/{download-TSo32ofd.js → download-5XbM3TL_.js} +1 -1
- package/dist/assets/edit-page-BDgzn0ig.js +9 -0
- package/dist/assets/{error-banner-bXc_9BBZ.js → error-banner-frvr6JXK.js} +1 -1
- package/dist/assets/{error-panel-aq2j0jIa.js → error-panel-DQOeSv5-.js} +1 -1
- package/dist/assets/{field-DTzXkFLZ.js → field-DaQqUJ5I.js} +1 -1
- package/dist/assets/{file-explorer-panel-bcSqGkbZ.js → file-explorer-panel-B67zjs2X.js} +1 -1
- package/dist/assets/{file-icons-DBaXCICA.js → file-icons-Bj5YoM7H.js} +1 -1
- package/dist/assets/{floating-outline-BTmyhMGv.js → floating-outline-XObNWtN8.js} +1 -1
- package/dist/assets/{focus-DXeddo75.js → focus-DzMo6UAI.js} +1 -1
- package/dist/assets/{form-30oC5z9y.js → form-BJ6VFU8l.js} +1 -1
- package/dist/assets/{gallery-page-XSrY7bw_.js → gallery-page-BDI9wEUZ.js} +1 -1
- package/dist/assets/{glide-data-editor-Bd4FOxvW.js → glide-data-editor-QI6B6uKw.js} +1 -1
- package/dist/assets/{globals-DQM2RvzM.js → globals-Bu6OEURn.js} +1 -1
- package/dist/assets/{home-page-CruHjoHv.js → home-page-BUdd5uTz.js} +1 -1
- package/dist/assets/{hooks-DLUrd-jH.js → hooks-DvwShzDb.js} +1 -1
- package/dist/assets/{html-to-image-BJiJlwQY.js → html-to-image-DGqJ93hW.js} +1 -1
- package/dist/assets/index-C9DyCFTe.js +42 -0
- package/dist/assets/index-CKRn_SiB.css +2 -0
- package/dist/assets/{input-Bg12i6qY.js → input-DNCT6U6R.js} +1 -1
- package/dist/assets/{kiosk-mode-JCcLyeoQ.js → kiosk-mode-DYHoqMaZ.js} +1 -1
- package/dist/assets/layout-erv8pLIP.js +9 -0
- package/dist/assets/{logs-panel-BzhPrie8.js → logs-panel-CRW4c2IL.js} +1 -1
- package/dist/assets/{markdown-renderer-B9RsGqHb.js → markdown-renderer-DNANigO8.js} +1 -1
- package/dist/assets/{mermaid-BJFSZcG6.js → mermaid-BPufPrIN.js} +1 -1
- package/dist/assets/{name-cell-input-CYsY4A1G.js → name-cell-input-3iKP6YTw.js} +1 -1
- package/dist/assets/{outline-panel-BCAWCKi6.js → outline-panel-VIqWcHj6.js} +1 -1
- package/dist/assets/{packages-panel-DxS7zji3.js → packages-panel-D_z4ylBE.js} +1 -1
- package/dist/assets/panels--5tTbFBo.js +1 -0
- package/dist/assets/{process-output-DqiZsqG9.js → process-output-Q6wVr7a-.js} +1 -1
- package/dist/assets/{readonly-python-code-D8ITm60r.js → readonly-python-code-CI_b818F.js} +1 -1
- package/dist/assets/{run-page-CI2eOA-G.js → run-page-DN26u83D.js} +1 -1
- package/dist/assets/{scratchpad-panel-Dwp8-2S1.js → scratchpad-panel-CnaiXtoJ.js} +1 -1
- package/dist/assets/{secrets-panel-C6X5jB8Q.js → secrets-panel-DWMqzwkS.js} +1 -1
- package/dist/assets/{select--zcABebs.js → select-BwwUWhww.js} +1 -1
- package/dist/assets/{session-panel-ryqVmqVd.js → session-panel-C68GBFwH.js} +1 -1
- package/dist/assets/{slides-component-DXMG6OXG.js → slides-component-ncUJNz7U.js} +1 -1
- package/dist/assets/{snippets-panel--mh2FUXA.js → snippets-panel-BmIdR0lc.js} +1 -1
- package/dist/assets/{state-CMxx6hcP.js → state-DAlJ-NbL.js} +1 -1
- package/dist/assets/{state-BDrig0S2.js → state-DPomuurt.js} +1 -1
- package/dist/assets/{switch-C6xjg01T.js → switch-YkPg_CVc.js} +1 -1
- package/dist/assets/{textarea-Cfp3upzK.js → textarea-CS2o3y4W.js} +1 -1
- package/dist/assets/{tracing-BExYhl1z.js → tracing-CPDDwzIA.js} +1 -1
- package/dist/assets/{tracing-panel-Co5DeX-F.js → tracing-panel-Ku1LapXJ.js} +2 -2
- package/dist/assets/{useAddCell-BaTlDxTu.js → useAddCell-B6yUY_RG.js} +1 -1
- package/dist/assets/{useBoolean-BvsK1Xcs.js → useBoolean-Dk1Mb_so.js} +1 -1
- package/dist/assets/{useCellActionButton-DftkIqUl.js → useCellActionButton-SxeK4dmW.js} +1 -1
- package/dist/assets/{useDeleteCell-d6yWnL3H.js → useDeleteCell-DHUjJQJx.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-BaVcOBM4.js → useDependencyPanelTab-CflgayoH.js} +1 -1
- package/dist/assets/{useNotebookActions-D8Dm93y8.js → useNotebookActions-Ch1o32Jw.js} +1 -1
- package/dist/assets/{useRunCells-d2edY6Tu.js → useRunCells-DFYAOTWd.js} +1 -1
- package/dist/assets/{useSplitCell-DOiFyMgH.js → useSplitCell-Bh-NZsBl.js} +1 -1
- package/dist/assets/{vega-component-CiVPyAwP.js → vega-component-DtSTT5wO.js} +1 -1
- package/dist/assets/{write-secret-modal-CHfFN0H8.js → write-secret-modal-CInxHVWJ.js} +1 -1
- package/dist/index.html +39 -39
- package/package.json +1 -1
- package/src/components/editor/renderers/slides-layout/slides-layout.tsx +50 -44
- package/src/components/slides/__tests__/minimap.test.ts +402 -0
- package/src/components/slides/minimap.tsx +534 -0
- package/src/components/slides/slide.tsx +29 -0
- package/src/components/slides/slides-component.tsx +16 -1
- package/src/components/ui/button.tsx +1 -2
- package/src/core/cells/__tests__/cells.test.ts +105 -1
- package/src/core/cells/cells.ts +43 -0
- package/src/core/cells/document-changes.ts +2 -1
- package/src/plugins/core/RenderHTML.tsx +9 -0
- package/src/plugins/core/__test__/RenderHTML.test.ts +27 -0
- package/src/plugins/core/registerReactComponent.tsx +11 -8
- package/src/plugins/impl/ButtonPlugin.tsx +4 -6
- package/src/plugins/impl/CodeEditorPlugin.tsx +15 -18
- package/src/plugins/impl/DataEditorPlugin.tsx +8 -14
- package/src/plugins/impl/DataTablePlugin.tsx +1 -6
- package/src/plugins/impl/FileUploadPlugin.tsx +39 -43
- package/src/plugins/impl/FormPlugin.tsx +2 -6
- package/src/plugins/impl/chat/ChatPlugin.tsx +17 -20
- package/src/plugins/impl/data-explorer/DataExplorerPlugin.tsx +5 -8
- package/src/plugins/impl/matplotlib/matplotlib-renderer.ts +38 -14
- package/src/plugins/impl/vega/VegaPlugin.tsx +5 -8
- package/src/plugins/layout/NavigationMenuPlugin.tsx +2 -6
- package/src/utils/__tests__/events.test.ts +223 -0
- package/src/utils/events.ts +28 -14
- package/dist/assets/RenderHTML-0dk6-mYI.js +0 -1
- package/dist/assets/button-BKVLeSTX.js +0 -1
- package/dist/assets/edit-page-fl9KJOcI.js +0 -13
- package/dist/assets/index-DTBKKey_.js +0 -42
- package/dist/assets/index-qO0a4zuT.css +0 -2
- package/dist/assets/layout-cq-nxDfO.js +0 -5
- 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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
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
|
+
});
|
package/src/utils/events.ts
CHANGED
|
@@ -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
|
|
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(
|
|
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: (
|
|
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
|
-
|
|
62
|
+
const target = Events.composedTarget(e);
|
|
64
63
|
return (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
(
|
|
69
|
-
(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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};
|