@marimo-team/islands 0.23.1-dev13 → 0.23.1-dev3

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 (38) hide show
  1. package/dist/{ConnectedDataExplorerComponent-D08JKcQg.js → ConnectedDataExplorerComponent-tho8l5GC.js} +11 -11
  2. package/dist/{any-language-editor-BIGc8RUt.js → any-language-editor-Caoda-MR.js} +4 -4
  3. package/dist/{button-DNlNlZY_.js → button-Cn59RsBF.js} +9 -5
  4. package/dist/{capabilities-Coe9eM9T.js → capabilities-Bml77Di-.js} +1 -1
  5. package/dist/{chat-ui-B9oZ19ii.js → chat-ui-D0rOcl77.js} +13 -13
  6. package/dist/{check-Cex3x9fD.js → check-C51PA02v.js} +1 -1
  7. package/dist/{copy-BRF7ryOP.js → copy-DzOz_Au7.js} +2 -2
  8. package/dist/{dist-D56NKWim.js → dist-CjLQ79mo.js} +2 -2
  9. package/dist/{error-banner-DexD-5js.js → error-banner-BQcJXy6k.js} +2 -2
  10. package/dist/{esm-BGo_Mcdt.js → esm-hRljRgfG.js} +4 -4
  11. package/dist/{glide-data-editor-BmyQCm0U.js → glide-data-editor-D2X4D_xi.js} +7 -7
  12. package/dist/{input-SSWXiS6n.js → input-BRN2Mjzx.js} +6 -6
  13. package/dist/{label-CIR53v8V.js → label-J1N4mVcg.js} +4 -4
  14. package/dist/{loader-Bd1kgLn7.js → loader-JmfPBSx5.js} +1 -1
  15. package/dist/main.js +38 -34
  16. package/dist/{mermaid-BZ2YHhbi.js → mermaid-UMKVHs_g.js} +5 -5
  17. package/dist/{process-output-D_uZ0o1x.js → process-output-C1_DucTc.js} +15 -15
  18. package/dist/{slides-component-CX2JC-Ws.js → slides-component-CutG23yD.js} +2 -2
  19. package/dist/{spec-ByDEU1T3.js → spec-rP6otrXw.js} +3 -3
  20. package/dist/style.css +1 -1
  21. package/dist/{toDate-D0QaHNwR.js → toDate-CfGr2xZ1.js} +3 -3
  22. package/dist/{tooltip-B5EnNyok.js → tooltip-CygUI9oH.js} +3 -3
  23. package/dist/{types-D4-TD_m0.js → types-D4CFUmPq.js} +2 -2
  24. package/dist/{useAsyncData-BG3ULuDU.js → useAsyncData-BzS3Ai32.js} +1 -1
  25. package/dist/{useDeepCompareMemoize-CkSq3l3_.js → useDeepCompareMemoize-AjORjhpL.js} +4 -4
  26. package/dist/{useIframeCapabilities-CqhrVue6.js → useIframeCapabilities---aRgGdv.js} +1 -1
  27. package/dist/{useLifecycle-2Vh-WDv6.js → useLifecycle-B90lJH4p.js} +3 -3
  28. package/dist/{useTheme-CxjbgkRc.js → useTheme-ytmywQ5O.js} +2 -2
  29. package/dist/{vega-component-z4WGXPkf.js → vega-component-1icm1Ykb.js} +9 -9
  30. package/dist/{zod-D18k8Z52.js → zod-D4UoCYGu.js} +1 -1
  31. package/package.json +1 -1
  32. package/src/components/data-table/TableTopBar.tsx +1 -1
  33. package/src/components/data-table/table-explorer-panel/table-explorer-panel.tsx +1 -1
  34. package/src/components/ui/button.tsx +1 -2
  35. package/src/css/table.css +4 -0
  36. package/src/plugins/impl/common/labeled.tsx +5 -3
  37. package/src/utils/__tests__/events.test.ts +223 -0
  38. package/src/utils/events.ts +28 -14
@@ -3,10 +3,10 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { t as __commonJSMin } from "./chunk-BNovOVIE.js";
5
5
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
6
- import { u as createLucideIcon } from "./dist-D56NKWim.js";
7
- import { g as Logger } from "./button-DNlNlZY_.js";
6
+ import { u as createLucideIcon } from "./dist-CjLQ79mo.js";
7
+ import { g as Logger } from "./button-Cn59RsBF.js";
8
8
  import { r as KnownQueryParams } from "./constants-CvyfaCvs.js";
9
- import { f as waitFor, p as isIslands, u as store, y as atom } from "./useTheme-CxjbgkRc.js";
9
+ import { f as waitFor, p as isIslands, u as store, y as atom } from "./useTheme-ytmywQ5O.js";
10
10
  var CircleQuestionMark = createLucideIcon("circle-question-mark", [
11
11
  ["circle", {
12
12
  cx: "12",
@@ -1,10 +1,10 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
- import { a as createPopperScope, i as Root2, n as Arrow, r as Content, s as Root, t as Anchor } from "./dist-D56NKWim.js";
5
- import { f as createSlottable, m as useComposedRefs, y as cn } from "./button-DNlNlZY_.js";
4
+ import { a as createPopperScope, i as Root2, n as Arrow, r as Content, s as Root, t as Anchor } from "./dist-CjLQ79mo.js";
5
+ import { f as createSlottable, m as useComposedRefs, y as cn } from "./button-Cn59RsBF.js";
6
6
  import { t as require_jsx_runtime } from "./jsx-runtime-9hcJiI23.js";
7
- import { $ as StyleNamespace, X as withFullScreenAsRoot, Z as withSmartCollisionBoundary, _t as Primitive, dt as Presence, ft as useControllableState, gt as createContextScope, it as Portal, mt as composeEventHandlers, st as DismissableLayer, ut as useId } from "./zod-D18k8Z52.js";
7
+ import { $ as StyleNamespace, X as withFullScreenAsRoot, Z as withSmartCollisionBoundary, _t as Primitive, dt as Presence, ft as useControllableState, gt as createContextScope, it as Portal, mt as composeEventHandlers, st as DismissableLayer, ut as useId } from "./zod-D4UoCYGu.js";
8
8
  var import_react = /* @__PURE__ */ __toESM(require_react(), 1), import_jsx_runtime = /* @__PURE__ */ __toESM(require_jsx_runtime(), 1), [createTooltipContext, createTooltipScope] = createContextScope("Tooltip", [createPopperScope]), usePopperScope = createPopperScope(), PROVIDER_NAME = "TooltipProvider", DEFAULT_DELAY_DURATION = 700, TOOLTIP_OPEN = "tooltip.open", [TooltipProviderContextProvider, useTooltipProviderContext] = createTooltipContext(PROVIDER_NAME), TooltipProvider$1 = (t) => {
9
9
  let { __scopeTooltip: j, delayDuration: M = DEFAULT_DELAY_DURATION, skipDelayDuration: N = 300, disableHoverableContent: P = false, children: F } = t, I = import_react.useRef(true), L = import_react.useRef(false), R = import_react.useRef(0);
10
10
  return import_react.useEffect(() => {
@@ -1,8 +1,8 @@
1
1
  import { s as __toESM, t as __commonJSMin } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
- import { u as createLucideIcon } from "./dist-D56NKWim.js";
5
- import { t as Button } from "./button-DNlNlZY_.js";
4
+ import { u as createLucideIcon } from "./dist-CjLQ79mo.js";
5
+ import { t as Button } from "./button-Cn59RsBF.js";
6
6
  import { t as require_jsx_runtime } from "./jsx-runtime-9hcJiI23.js";
7
7
  import { n as Constants } from "./constants-CvyfaCvs.js";
8
8
  var Pencil = createLucideIcon("pencil", [["path", {
@@ -1,7 +1,7 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
- import { w as useEvent_default } from "./useTheme-CxjbgkRc.js";
4
+ import { w as useEvent_default } from "./useTheme-ytmywQ5O.js";
5
5
  import { t as invariant } from "./invariant-e8eBgdux.js";
6
6
  var import_compiler_runtime = require_compiler_runtime(), import_react = /* @__PURE__ */ __toESM(require_react(), 1), Result = {
7
7
  error(e, s) {
@@ -1,11 +1,11 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
- import { t as toDate } from "./toDate-D0QaHNwR.js";
5
- import { r as cva, y as cn } from "./button-DNlNlZY_.js";
4
+ import { t as toDate } from "./toDate-CfGr2xZ1.js";
5
+ import { r as cva, y as cn } from "./button-Cn59RsBF.js";
6
6
  import { t as require_jsx_runtime } from "./jsx-runtime-9hcJiI23.js";
7
- import { C as dequal } from "./useTheme-CxjbgkRc.js";
8
- import { i as tableFromIPC } from "./loader-Bd1kgLn7.js";
7
+ import { C as dequal } from "./useTheme-ytmywQ5O.js";
8
+ import { i as tableFromIPC } from "./loader-JmfPBSx5.js";
9
9
  function isDate(t) {
10
10
  return t instanceof Date || typeof t == "object" && Object.prototype.toString.call(t) === "[object Date]";
11
11
  }
@@ -1,7 +1,7 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
- import { t as getIframeCapabilities } from "./capabilities-Coe9eM9T.js";
4
+ import { t as getIframeCapabilities } from "./capabilities-Bml77Di-.js";
5
5
  var import_compiler_runtime = require_compiler_runtime();
6
6
  require_react();
7
7
  function useIframeCapabilities() {
@@ -1,10 +1,10 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
- import { u as createLucideIcon } from "./dist-D56NKWim.js";
5
- import { g as Logger, r as cva, y as cn } from "./button-DNlNlZY_.js";
4
+ import { u as createLucideIcon } from "./dist-CjLQ79mo.js";
5
+ import { g as Logger, r as cva, y as cn } from "./button-Cn59RsBF.js";
6
6
  import { t as require_jsx_runtime } from "./jsx-runtime-9hcJiI23.js";
7
- import { _ as useSetAtom, y as atom } from "./useTheme-CxjbgkRc.js";
7
+ import { _ as useSetAtom, y as atom } from "./useTheme-ytmywQ5O.js";
8
8
  var Calendar = createLucideIcon("calendar", [
9
9
  ["path", {
10
10
  d: "M8 2v4",
@@ -1,8 +1,8 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
- import { a as OverridingHotkeyProvider, g as Logger, s as resolvePlatform } from "./button-DNlNlZY_.js";
5
- import { A as looseObject, B as union, I as record, N as number, P as object, R as string, T as boolean, b as _enum, w as array } from "./zod-D18k8Z52.js";
4
+ import { a as OverridingHotkeyProvider, g as Logger, s as resolvePlatform } from "./button-Cn59RsBF.js";
5
+ import { A as looseObject, B as union, I as record, N as number, P as object, R as string, T as boolean, b as _enum, w as array } from "./zod-D4UoCYGu.js";
6
6
  import { t as merge_default } from "./merge-CVhG7q_o.js";
7
7
  var import_react = /* @__PURE__ */ __toESM(require_react()), useInsertionEffect = typeof window < "u" ? import_react.useInsertionEffect || import_react.useLayoutEffect : () => {
8
8
  };
@@ -1,20 +1,20 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
- import { c as asRemoteURL, g as CircleQuestionMark } from "./toDate-D0QaHNwR.js";
5
- import { c as Objects, g as Logger, h as Events, y as cn } from "./button-DNlNlZY_.js";
4
+ import { c as asRemoteURL, g as CircleQuestionMark } from "./toDate-CfGr2xZ1.js";
5
+ import { c as Objects, g as Logger, h as Events, y as cn } from "./button-Cn59RsBF.js";
6
6
  import "./react-dom-BSUuJjCR.js";
7
7
  import { t as require_jsx_runtime } from "./jsx-runtime-9hcJiI23.js";
8
- import "./zod-D18k8Z52.js";
9
- import { n as ErrorBanner } from "./error-banner-DexD-5js.js";
10
- import { t as Tooltip } from "./tooltip-B5EnNyok.js";
8
+ import "./zod-D4UoCYGu.js";
9
+ import { n as ErrorBanner } from "./error-banner-BQcJXy6k.js";
10
+ import { t as Tooltip } from "./tooltip-CygUI9oH.js";
11
11
  import { i as debounce_default } from "./constants-CvyfaCvs.js";
12
- import { n as useTheme, w as useEvent_default } from "./useTheme-CxjbgkRc.js";
12
+ import { n as useTheme, w as useEvent_default } from "./useTheme-ytmywQ5O.js";
13
13
  import { s as uniq } from "./arrays-beUWo8RF.js";
14
- import { a as AlertTitle, n as arrow, o as isValid, r as Alert, t as useDeepCompareMemoize } from "./useDeepCompareMemoize-CkSq3l3_.js";
14
+ import { a as AlertTitle, n as arrow, o as isValid, r as Alert, t as useDeepCompareMemoize } from "./useDeepCompareMemoize-AjORjhpL.js";
15
15
  import { n as formats } from "./vega-loader.browser-DqEcFOPD.js";
16
- import { a as getContainerWidth, n as vegaLoadData, s as tooltipHandler } from "./loader-Bd1kgLn7.js";
17
- import { t as useAsyncData } from "./useAsyncData-BG3ULuDU.js";
16
+ import { a as getContainerWidth, n as vegaLoadData, s as tooltipHandler } from "./loader-JmfPBSx5.js";
17
+ import { t as useAsyncData } from "./useAsyncData-BzS3Ai32.js";
18
18
  import { t as j } from "./react-vega-CzRAIHrv.js";
19
19
  import "./defaultLocale-qS7DaAmi.js";
20
20
  import "./defaultLocale-Bxoo2-30.js";
@@ -1,7 +1,7 @@
1
1
  import { r as __export, s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
- import { d as createSlot, l as useEventListener, m as useComposedRefs } from "./button-DNlNlZY_.js";
4
+ import { d as createSlot, l as useEventListener, m as useComposedRefs } from "./button-Cn59RsBF.js";
5
5
  import { t as require_react_dom } from "./react-dom-BSUuJjCR.js";
6
6
  import { t as require_jsx_runtime } from "./jsx-runtime-9hcJiI23.js";
7
7
  var import_react = /* @__PURE__ */ __toESM(require_react(), 1), import_react_dom$1 = /* @__PURE__ */ __toESM(require_react_dom(), 1), import_jsx_runtime = /* @__PURE__ */ __toESM(require_jsx_runtime(), 1), Primitive = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.23.1-dev13",
3
+ "version": "0.23.1-dev3",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -127,7 +127,7 @@ export const TableTopBar: React.FC<TableTopBarProps> = ({
127
127
  onClick={() => togglePanel(PANEL_TYPES.ROW_VIEWER)}
128
128
  >
129
129
  <PanelRightIcon className="w-3.5 h-3.5" />
130
- Explorer
130
+ Explore
131
131
  </Button>
132
132
  )}
133
133
  {downloadAs && <ExportMenu downloadAs={downloadAs} />}
@@ -139,7 +139,7 @@ export const TableExplorerPanel: React.FC<TableExplorerPanelProps> = ({
139
139
  : inactiveClassName,
140
140
  )}
141
141
  >
142
- Explorer
142
+ Columns
143
143
  </Button>
144
144
  </div>
145
145
  </Fill>
@@ -114,11 +114,10 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
114
114
 
115
115
  const handleKeyPress = React.useCallback(
116
116
  (e: KeyboardEvent) => {
117
- if (!keyboardShortcut) {
117
+ if (!keyboardShortcut || e.defaultPrevented) {
118
118
  return;
119
119
  }
120
120
 
121
- // Ignore keyboard events from input elements
122
121
  if (Events.shouldIgnoreKeyboardEvent(e)) {
123
122
  return;
124
123
  }
package/src/css/table.css CHANGED
@@ -17,6 +17,10 @@
17
17
  padding-right: var(--marimo-table-edge-padding, 0.375rem);
18
18
  }
19
19
 
20
+ [part="label"] {
21
+ padding-left: var(--marimo-table-edge-padding, 0);
22
+ }
23
+
20
24
  .markdown table,
21
25
  table.dataframe {
22
26
  display: block;
@@ -44,9 +44,11 @@ export const Labeled: React.FC<PropsWithChildren<Props>> = ({
44
44
  }
45
45
 
46
46
  const labelElement = (
47
- <Label htmlFor={id} className={cn("font-prose", labelClassName)}>
48
- {renderHTML({ html: label })}
49
- </Label>
47
+ <div part="label" className="m-0 p-0">
48
+ <Label htmlFor={id} className={cn("font-prose", labelClassName)}>
49
+ {renderHTML({ html: label })}
50
+ </Label>
51
+ </div>
50
52
  );
51
53
 
52
54
  return (
@@ -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
  ) => {