@seed-ship/mcp-ui-solid 6.3.1 → 6.4.0
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/CHANGELOG.md +56 -0
- package/dist/components/GraphRenderer.cjs +30 -24
- package/dist/components/GraphRenderer.cjs.map +1 -1
- package/dist/components/GraphRenderer.d.ts.map +1 -1
- package/dist/components/GraphRenderer.js +30 -24
- package/dist/components/GraphRenderer.js.map +1 -1
- package/dist/components/PortalDropdownMenu.cjs +82 -0
- package/dist/components/PortalDropdownMenu.cjs.map +1 -0
- package/dist/components/PortalDropdownMenu.d.ts +56 -0
- package/dist/components/PortalDropdownMenu.d.ts.map +1 -0
- package/dist/components/PortalDropdownMenu.js +82 -0
- package/dist/components/PortalDropdownMenu.js.map +1 -0
- package/dist/components/UIResourceRenderer.cjs +260 -256
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +260 -256
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components.cjs +2 -0
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.cts +2 -0
- package/dist/components.d.ts +2 -0
- package/dist/components.js +2 -0
- package/dist/components.js.map +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/GraphRenderer.tsx +29 -20
- package/src/components/PortalDropdownMenu.test.tsx +113 -0
- package/src/components/PortalDropdownMenu.tsx +130 -0
- package/src/components/UIResourceRenderer.tsx +22 -13
- package/src/components/index.ts +4 -0
- package/src/index.ts +2 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const web = require("solid-js/web");
|
|
4
|
+
const solidJs = require("solid-js");
|
|
5
|
+
var _tmpl$ = /* @__PURE__ */ web.template(`<div role=menu style=position:fixed;z-index:9999>`);
|
|
6
|
+
const PortalDropdownMenu = (props) => {
|
|
7
|
+
const [position, setPosition] = solidJs.createSignal({
|
|
8
|
+
top: 0,
|
|
9
|
+
left: 0
|
|
10
|
+
});
|
|
11
|
+
let menuRef;
|
|
12
|
+
const updatePosition = () => {
|
|
13
|
+
const t = props.trigger;
|
|
14
|
+
if (!t) return;
|
|
15
|
+
const rect = t.getBoundingClientRect();
|
|
16
|
+
const w = props.width ?? 144;
|
|
17
|
+
setPosition({
|
|
18
|
+
top: rect.bottom + 4,
|
|
19
|
+
left: rect.right - w
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
solidJs.createEffect(() => {
|
|
23
|
+
if (!props.open) return;
|
|
24
|
+
updatePosition();
|
|
25
|
+
const onDown = (e) => {
|
|
26
|
+
var _a;
|
|
27
|
+
const target = e.target;
|
|
28
|
+
if ((menuRef == null ? void 0 : menuRef.contains(target)) || ((_a = props.trigger) == null ? void 0 : _a.contains(target))) return;
|
|
29
|
+
props.onClose();
|
|
30
|
+
};
|
|
31
|
+
const onKey = (e) => {
|
|
32
|
+
if (e.key === "Escape") props.onClose();
|
|
33
|
+
};
|
|
34
|
+
const onScrollOrResize = () => updatePosition();
|
|
35
|
+
document.addEventListener("mousedown", onDown);
|
|
36
|
+
document.addEventListener("keydown", onKey);
|
|
37
|
+
window.addEventListener("scroll", onScrollOrResize, {
|
|
38
|
+
capture: true,
|
|
39
|
+
passive: true
|
|
40
|
+
});
|
|
41
|
+
window.addEventListener("resize", onScrollOrResize);
|
|
42
|
+
solidJs.onCleanup(() => {
|
|
43
|
+
document.removeEventListener("mousedown", onDown);
|
|
44
|
+
document.removeEventListener("keydown", onKey);
|
|
45
|
+
window.removeEventListener("scroll", onScrollOrResize, {
|
|
46
|
+
capture: true
|
|
47
|
+
});
|
|
48
|
+
window.removeEventListener("resize", onScrollOrResize);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
return web.createComponent(solidJs.Show, {
|
|
52
|
+
get when() {
|
|
53
|
+
return props.open;
|
|
54
|
+
},
|
|
55
|
+
get children() {
|
|
56
|
+
return web.createComponent(web.Portal, {
|
|
57
|
+
get children() {
|
|
58
|
+
var _el$ = web.getNextElement(_tmpl$);
|
|
59
|
+
var _ref$ = menuRef;
|
|
60
|
+
typeof _ref$ === "function" ? web.use(_ref$, _el$) : menuRef = _el$;
|
|
61
|
+
web.insert(_el$, () => props.children);
|
|
62
|
+
web.effect((_p$) => {
|
|
63
|
+
var _v$ = `${position().top}px`, _v$2 = `${position().left}px`, _v$3 = `${props.width ?? 144}px`, _v$4 = `bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg py-1 text-sm ${props.class ?? ""}`;
|
|
64
|
+
_v$ !== _p$.e && web.setStyleProperty(_el$, "top", _p$.e = _v$);
|
|
65
|
+
_v$2 !== _p$.t && web.setStyleProperty(_el$, "left", _p$.t = _v$2);
|
|
66
|
+
_v$3 !== _p$.a && web.setStyleProperty(_el$, "width", _p$.a = _v$3);
|
|
67
|
+
_v$4 !== _p$.o && web.className(_el$, _p$.o = _v$4);
|
|
68
|
+
return _p$;
|
|
69
|
+
}, {
|
|
70
|
+
e: void 0,
|
|
71
|
+
t: void 0,
|
|
72
|
+
a: void 0,
|
|
73
|
+
o: void 0
|
|
74
|
+
});
|
|
75
|
+
return _el$;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
exports.PortalDropdownMenu = PortalDropdownMenu;
|
|
82
|
+
//# sourceMappingURL=PortalDropdownMenu.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PortalDropdownMenu.cjs","sources":["../../src/components/PortalDropdownMenu.tsx"],"sourcesContent":["/**\n * PortalDropdownMenu (v6.4.0) — generic dropdown that mounts via\n * `<Portal>` on `document.body` instead of an in-tree `position: absolute`\n * sibling. Eliminates two pain points around the legacy in-tree pattern :\n *\n * 1. **`overflow: hidden` clipping** — when the trigger lives inside a\n * chat bubble or a card with `overflow: hidden`, an absolutely\n * positioned menu sibling gets clipped at the ancestor's boundary.\n * Mounting on `document.body` escapes the clip stack entirely.\n * 2. **`z-index` wars** — chat surfaces stack composer / message rails\n * above the message list, and ancestor `z-index` creates a new\n * stacking context that captures the in-tree menu. A portal is a\n * sibling of the document, so a single `z-index: 9999` wins.\n *\n * The menu is positioned with `position: fixed` from the trigger's\n * `getBoundingClientRect()`. We re-measure on `scroll` (capture phase, so\n * nested scrollables also fire) and `resize` to keep the menu pinned\n * while the user interacts with surrounding chrome.\n *\n * Close affordances : click outside, Escape, programmatic via `onClose`.\n */\n\nimport { Component, JSX, Show, createSignal, createEffect, onCleanup } from 'solid-js'\nimport { Portal } from 'solid-js/web'\n\nexport interface PortalDropdownMenuProps {\n /**\n * Whether the menu is currently open. Controlled by the parent.\n */\n open: boolean\n\n /**\n * Called when the menu wants to close (outside click / Escape / item\n * click — it's the parent's job to actually flip `open` to false).\n */\n onClose: () => void\n\n /**\n * Trigger element used as the positioning anchor. The menu's right\n * edge is aligned to the trigger's right edge, top to its bottom + 4px.\n */\n trigger: HTMLElement | undefined\n\n /**\n * Menu width in pixels. Used to compute the left coordinate so the menu's\n * right edge aligns with the trigger's right edge. Default : `144` (the\n * legacy table menu width — `w-36`).\n */\n width?: number\n\n /**\n * Menu content. Wrapped in the rounded / shadowed container — keep\n * children minimal (just the items).\n */\n children: JSX.Element\n\n /**\n * Optional additional class names for the menu container, appended after\n * the default Tailwind classes. Useful to override width or padding.\n */\n class?: string\n}\n\nexport const PortalDropdownMenu: Component<PortalDropdownMenuProps> = (props) => {\n const [position, setPosition] = createSignal({ top: 0, left: 0 })\n let menuRef: HTMLDivElement | undefined\n\n const updatePosition = () => {\n const t = props.trigger\n if (!t) return\n const rect = t.getBoundingClientRect()\n const w = props.width ?? 144\n setPosition({\n top: rect.bottom + 4,\n left: rect.right - w,\n })\n }\n\n // Measure once when the menu opens, then react to viewport changes.\n createEffect(() => {\n if (!props.open) return\n updatePosition()\n\n const onDown = (e: MouseEvent) => {\n const target = e.target as Node\n if (menuRef?.contains(target) || props.trigger?.contains(target)) return\n props.onClose()\n }\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') props.onClose()\n }\n const onScrollOrResize = () => updatePosition()\n\n document.addEventListener('mousedown', onDown)\n document.addEventListener('keydown', onKey)\n // Capture phase so scrolls inside nested containers (e.g. chat virtual\n // list) also re-position the menu. `passive: true` because we never\n // preventDefault here.\n window.addEventListener('scroll', onScrollOrResize, { capture: true, passive: true })\n window.addEventListener('resize', onScrollOrResize)\n\n onCleanup(() => {\n document.removeEventListener('mousedown', onDown)\n document.removeEventListener('keydown', onKey)\n window.removeEventListener('scroll', onScrollOrResize, { capture: true })\n window.removeEventListener('resize', onScrollOrResize)\n })\n })\n\n return (\n <Show when={props.open}>\n <Portal>\n <div\n ref={menuRef}\n role=\"menu\"\n style={{\n position: 'fixed',\n top: `${position().top}px`,\n left: `${position().left}px`,\n 'z-index': 9999,\n width: `${props.width ?? 144}px`,\n }}\n class={`bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg py-1 text-sm ${props.class ?? ''}`}\n >\n {props.children}\n </div>\n </Portal>\n </Show>\n )\n}\n"],"names":["PortalDropdownMenu","props","position","setPosition","createSignal","top","left","menuRef","updatePosition","t","trigger","rect","getBoundingClientRect","w","width","bottom","right","createEffect","open","onDown","e","target","contains","onClose","onKey","key","onScrollOrResize","document","addEventListener","window","capture","passive","onCleanup","removeEventListener","_$createComponent","Show","when","children","Portal","_el$","_$getNextElement","_tmpl$","_ref$","_$use","_$insert","_$effect","_p$","_v$","_v$2","_v$3","_v$4","class","_$setStyleProperty","a","o","_$className","undefined"],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PortalDropdownMenu (v6.4.0) — generic dropdown that mounts via
|
|
3
|
+
* `<Portal>` on `document.body` instead of an in-tree `position: absolute`
|
|
4
|
+
* sibling. Eliminates two pain points around the legacy in-tree pattern :
|
|
5
|
+
*
|
|
6
|
+
* 1. **`overflow: hidden` clipping** — when the trigger lives inside a
|
|
7
|
+
* chat bubble or a card with `overflow: hidden`, an absolutely
|
|
8
|
+
* positioned menu sibling gets clipped at the ancestor's boundary.
|
|
9
|
+
* Mounting on `document.body` escapes the clip stack entirely.
|
|
10
|
+
* 2. **`z-index` wars** — chat surfaces stack composer / message rails
|
|
11
|
+
* above the message list, and ancestor `z-index` creates a new
|
|
12
|
+
* stacking context that captures the in-tree menu. A portal is a
|
|
13
|
+
* sibling of the document, so a single `z-index: 9999` wins.
|
|
14
|
+
*
|
|
15
|
+
* The menu is positioned with `position: fixed` from the trigger's
|
|
16
|
+
* `getBoundingClientRect()`. We re-measure on `scroll` (capture phase, so
|
|
17
|
+
* nested scrollables also fire) and `resize` to keep the menu pinned
|
|
18
|
+
* while the user interacts with surrounding chrome.
|
|
19
|
+
*
|
|
20
|
+
* Close affordances : click outside, Escape, programmatic via `onClose`.
|
|
21
|
+
*/
|
|
22
|
+
import { Component, JSX } from 'solid-js';
|
|
23
|
+
export interface PortalDropdownMenuProps {
|
|
24
|
+
/**
|
|
25
|
+
* Whether the menu is currently open. Controlled by the parent.
|
|
26
|
+
*/
|
|
27
|
+
open: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Called when the menu wants to close (outside click / Escape / item
|
|
30
|
+
* click — it's the parent's job to actually flip `open` to false).
|
|
31
|
+
*/
|
|
32
|
+
onClose: () => void;
|
|
33
|
+
/**
|
|
34
|
+
* Trigger element used as the positioning anchor. The menu's right
|
|
35
|
+
* edge is aligned to the trigger's right edge, top to its bottom + 4px.
|
|
36
|
+
*/
|
|
37
|
+
trigger: HTMLElement | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Menu width in pixels. Used to compute the left coordinate so the menu's
|
|
40
|
+
* right edge aligns with the trigger's right edge. Default : `144` (the
|
|
41
|
+
* legacy table menu width — `w-36`).
|
|
42
|
+
*/
|
|
43
|
+
width?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Menu content. Wrapped in the rounded / shadowed container — keep
|
|
46
|
+
* children minimal (just the items).
|
|
47
|
+
*/
|
|
48
|
+
children: JSX.Element;
|
|
49
|
+
/**
|
|
50
|
+
* Optional additional class names for the menu container, appended after
|
|
51
|
+
* the default Tailwind classes. Useful to override width or padding.
|
|
52
|
+
*/
|
|
53
|
+
class?: string;
|
|
54
|
+
}
|
|
55
|
+
export declare const PortalDropdownMenu: Component<PortalDropdownMenuProps>;
|
|
56
|
+
//# sourceMappingURL=PortalDropdownMenu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PortalDropdownMenu.d.ts","sourceRoot":"","sources":["../../src/components/PortalDropdownMenu.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,SAAS,EAAE,GAAG,EAA+C,MAAM,UAAU,CAAA;AAGtF,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,IAAI,EAAE,OAAO,CAAA;IAEb;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAA;IAEnB;;;OAGG;IACH,OAAO,EAAE,WAAW,GAAG,SAAS,CAAA;IAEhC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd;;;OAGG;IACH,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAA;IAErB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,eAAO,MAAM,kBAAkB,EAAE,SAAS,CAAC,uBAAuB,CAkEjE,CAAA"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createComponent, Portal, getNextElement, template, insert, effect, setStyleProperty, className, use } from "solid-js/web";
|
|
2
|
+
import { createSignal, createEffect, onCleanup, Show } from "solid-js";
|
|
3
|
+
var _tmpl$ = /* @__PURE__ */ template(`<div role=menu style=position:fixed;z-index:9999>`);
|
|
4
|
+
const PortalDropdownMenu = (props) => {
|
|
5
|
+
const [position, setPosition] = createSignal({
|
|
6
|
+
top: 0,
|
|
7
|
+
left: 0
|
|
8
|
+
});
|
|
9
|
+
let menuRef;
|
|
10
|
+
const updatePosition = () => {
|
|
11
|
+
const t = props.trigger;
|
|
12
|
+
if (!t) return;
|
|
13
|
+
const rect = t.getBoundingClientRect();
|
|
14
|
+
const w = props.width ?? 144;
|
|
15
|
+
setPosition({
|
|
16
|
+
top: rect.bottom + 4,
|
|
17
|
+
left: rect.right - w
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
createEffect(() => {
|
|
21
|
+
if (!props.open) return;
|
|
22
|
+
updatePosition();
|
|
23
|
+
const onDown = (e) => {
|
|
24
|
+
var _a;
|
|
25
|
+
const target = e.target;
|
|
26
|
+
if ((menuRef == null ? void 0 : menuRef.contains(target)) || ((_a = props.trigger) == null ? void 0 : _a.contains(target))) return;
|
|
27
|
+
props.onClose();
|
|
28
|
+
};
|
|
29
|
+
const onKey = (e) => {
|
|
30
|
+
if (e.key === "Escape") props.onClose();
|
|
31
|
+
};
|
|
32
|
+
const onScrollOrResize = () => updatePosition();
|
|
33
|
+
document.addEventListener("mousedown", onDown);
|
|
34
|
+
document.addEventListener("keydown", onKey);
|
|
35
|
+
window.addEventListener("scroll", onScrollOrResize, {
|
|
36
|
+
capture: true,
|
|
37
|
+
passive: true
|
|
38
|
+
});
|
|
39
|
+
window.addEventListener("resize", onScrollOrResize);
|
|
40
|
+
onCleanup(() => {
|
|
41
|
+
document.removeEventListener("mousedown", onDown);
|
|
42
|
+
document.removeEventListener("keydown", onKey);
|
|
43
|
+
window.removeEventListener("scroll", onScrollOrResize, {
|
|
44
|
+
capture: true
|
|
45
|
+
});
|
|
46
|
+
window.removeEventListener("resize", onScrollOrResize);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
return createComponent(Show, {
|
|
50
|
+
get when() {
|
|
51
|
+
return props.open;
|
|
52
|
+
},
|
|
53
|
+
get children() {
|
|
54
|
+
return createComponent(Portal, {
|
|
55
|
+
get children() {
|
|
56
|
+
var _el$ = getNextElement(_tmpl$);
|
|
57
|
+
var _ref$ = menuRef;
|
|
58
|
+
typeof _ref$ === "function" ? use(_ref$, _el$) : menuRef = _el$;
|
|
59
|
+
insert(_el$, () => props.children);
|
|
60
|
+
effect((_p$) => {
|
|
61
|
+
var _v$ = `${position().top}px`, _v$2 = `${position().left}px`, _v$3 = `${props.width ?? 144}px`, _v$4 = `bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg py-1 text-sm ${props.class ?? ""}`;
|
|
62
|
+
_v$ !== _p$.e && setStyleProperty(_el$, "top", _p$.e = _v$);
|
|
63
|
+
_v$2 !== _p$.t && setStyleProperty(_el$, "left", _p$.t = _v$2);
|
|
64
|
+
_v$3 !== _p$.a && setStyleProperty(_el$, "width", _p$.a = _v$3);
|
|
65
|
+
_v$4 !== _p$.o && className(_el$, _p$.o = _v$4);
|
|
66
|
+
return _p$;
|
|
67
|
+
}, {
|
|
68
|
+
e: void 0,
|
|
69
|
+
t: void 0,
|
|
70
|
+
a: void 0,
|
|
71
|
+
o: void 0
|
|
72
|
+
});
|
|
73
|
+
return _el$;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
export {
|
|
80
|
+
PortalDropdownMenu
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=PortalDropdownMenu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PortalDropdownMenu.js","sources":["../../src/components/PortalDropdownMenu.tsx"],"sourcesContent":["/**\n * PortalDropdownMenu (v6.4.0) — generic dropdown that mounts via\n * `<Portal>` on `document.body` instead of an in-tree `position: absolute`\n * sibling. Eliminates two pain points around the legacy in-tree pattern :\n *\n * 1. **`overflow: hidden` clipping** — when the trigger lives inside a\n * chat bubble or a card with `overflow: hidden`, an absolutely\n * positioned menu sibling gets clipped at the ancestor's boundary.\n * Mounting on `document.body` escapes the clip stack entirely.\n * 2. **`z-index` wars** — chat surfaces stack composer / message rails\n * above the message list, and ancestor `z-index` creates a new\n * stacking context that captures the in-tree menu. A portal is a\n * sibling of the document, so a single `z-index: 9999` wins.\n *\n * The menu is positioned with `position: fixed` from the trigger's\n * `getBoundingClientRect()`. We re-measure on `scroll` (capture phase, so\n * nested scrollables also fire) and `resize` to keep the menu pinned\n * while the user interacts with surrounding chrome.\n *\n * Close affordances : click outside, Escape, programmatic via `onClose`.\n */\n\nimport { Component, JSX, Show, createSignal, createEffect, onCleanup } from 'solid-js'\nimport { Portal } from 'solid-js/web'\n\nexport interface PortalDropdownMenuProps {\n /**\n * Whether the menu is currently open. Controlled by the parent.\n */\n open: boolean\n\n /**\n * Called when the menu wants to close (outside click / Escape / item\n * click — it's the parent's job to actually flip `open` to false).\n */\n onClose: () => void\n\n /**\n * Trigger element used as the positioning anchor. The menu's right\n * edge is aligned to the trigger's right edge, top to its bottom + 4px.\n */\n trigger: HTMLElement | undefined\n\n /**\n * Menu width in pixels. Used to compute the left coordinate so the menu's\n * right edge aligns with the trigger's right edge. Default : `144` (the\n * legacy table menu width — `w-36`).\n */\n width?: number\n\n /**\n * Menu content. Wrapped in the rounded / shadowed container — keep\n * children minimal (just the items).\n */\n children: JSX.Element\n\n /**\n * Optional additional class names for the menu container, appended after\n * the default Tailwind classes. Useful to override width or padding.\n */\n class?: string\n}\n\nexport const PortalDropdownMenu: Component<PortalDropdownMenuProps> = (props) => {\n const [position, setPosition] = createSignal({ top: 0, left: 0 })\n let menuRef: HTMLDivElement | undefined\n\n const updatePosition = () => {\n const t = props.trigger\n if (!t) return\n const rect = t.getBoundingClientRect()\n const w = props.width ?? 144\n setPosition({\n top: rect.bottom + 4,\n left: rect.right - w,\n })\n }\n\n // Measure once when the menu opens, then react to viewport changes.\n createEffect(() => {\n if (!props.open) return\n updatePosition()\n\n const onDown = (e: MouseEvent) => {\n const target = e.target as Node\n if (menuRef?.contains(target) || props.trigger?.contains(target)) return\n props.onClose()\n }\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') props.onClose()\n }\n const onScrollOrResize = () => updatePosition()\n\n document.addEventListener('mousedown', onDown)\n document.addEventListener('keydown', onKey)\n // Capture phase so scrolls inside nested containers (e.g. chat virtual\n // list) also re-position the menu. `passive: true` because we never\n // preventDefault here.\n window.addEventListener('scroll', onScrollOrResize, { capture: true, passive: true })\n window.addEventListener('resize', onScrollOrResize)\n\n onCleanup(() => {\n document.removeEventListener('mousedown', onDown)\n document.removeEventListener('keydown', onKey)\n window.removeEventListener('scroll', onScrollOrResize, { capture: true })\n window.removeEventListener('resize', onScrollOrResize)\n })\n })\n\n return (\n <Show when={props.open}>\n <Portal>\n <div\n ref={menuRef}\n role=\"menu\"\n style={{\n position: 'fixed',\n top: `${position().top}px`,\n left: `${position().left}px`,\n 'z-index': 9999,\n width: `${props.width ?? 144}px`,\n }}\n class={`bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg py-1 text-sm ${props.class ?? ''}`}\n >\n {props.children}\n </div>\n </Portal>\n </Show>\n )\n}\n"],"names":["PortalDropdownMenu","props","position","setPosition","createSignal","top","left","menuRef","updatePosition","t","trigger","rect","getBoundingClientRect","w","width","bottom","right","createEffect","open","onDown","e","target","contains","onClose","onKey","key","onScrollOrResize","document","addEventListener","window","capture","passive","onCleanup","removeEventListener","_$createComponent","Show","when","children","Portal","_el$","_$getNextElement","_tmpl$","_ref$","_$use","_$insert","_$effect","_p$","_v$","_v$2","_v$3","_v$4","class","_$setStyleProperty","a","o","_$className","undefined"],"mappings":";;;AA+DO,MAAMA,qBAA0DC,CAAAA,UAAU;AAC/E,QAAM,CAACC,UAAUC,WAAW,IAAIC,aAAa;AAAA,IAAEC,KAAK;AAAA,IAAGC,MAAM;AAAA,EAAA,CAAG;AAChE,MAAIC;AAEJ,QAAMC,iBAAiBA,MAAM;AAC3B,UAAMC,IAAIR,MAAMS;AAChB,QAAI,CAACD,EAAG;AACR,UAAME,OAAOF,EAAEG,sBAAAA;AACf,UAAMC,IAAIZ,MAAMa,SAAS;AACzBX,gBAAY;AAAA,MACVE,KAAKM,KAAKI,SAAS;AAAA,MACnBT,MAAMK,KAAKK,QAAQH;AAAAA,IAAAA,CACpB;AAAA,EACH;AAGAI,eAAa,MAAM;AACjB,QAAI,CAAChB,MAAMiB,KAAM;AACjBV,mBAAAA;AAEA,UAAMW,SAASA,CAACC,MAAkB;;AAChC,YAAMC,SAASD,EAAEC;AACjB,WAAId,mCAASe,SAASD,cAAWpB,WAAMS,YAANT,mBAAeqB,SAASD,SAAS;AAClEpB,YAAMsB,QAAAA;AAAAA,IACR;AACA,UAAMC,QAAQA,CAACJ,MAAqB;AAClC,UAAIA,EAAEK,QAAQ,SAAUxB,OAAMsB,QAAAA;AAAAA,IAChC;AACA,UAAMG,mBAAmBA,MAAMlB,eAAAA;AAE/BmB,aAASC,iBAAiB,aAAaT,MAAM;AAC7CQ,aAASC,iBAAiB,WAAWJ,KAAK;AAI1CK,WAAOD,iBAAiB,UAAUF,kBAAkB;AAAA,MAAEI,SAAS;AAAA,MAAMC,SAAS;AAAA,IAAA,CAAM;AACpFF,WAAOD,iBAAiB,UAAUF,gBAAgB;AAElDM,cAAU,MAAM;AACdL,eAASM,oBAAoB,aAAad,MAAM;AAChDQ,eAASM,oBAAoB,WAAWT,KAAK;AAC7CK,aAAOI,oBAAoB,UAAUP,kBAAkB;AAAA,QAAEI,SAAS;AAAA,MAAA,CAAM;AACxED,aAAOI,oBAAoB,UAAUP,gBAAgB;AAAA,IACvD,CAAC;AAAA,EACH,CAAC;AAED,SAAAQ,gBACGC,MAAI;AAAA,IAAA,IAACC,OAAI;AAAA,aAAEnC,MAAMiB;AAAAA,IAAI;AAAA,IAAA,IAAAmB,WAAA;AAAA,aAAAH,gBACnBI,QAAM;AAAA,QAAA,IAAAD,WAAA;AAAA,cAAAE,OAAAC,eAAAC,MAAA;AAAA,cAAAC,QAEEnC;AAAO,iBAAAmC,UAAA,aAAAC,IAAAD,OAAAH,IAAA,IAAPhC,UAAOgC;AAAAK,iBAAAL,MAAA,MAWXtC,MAAMoC,QAAQ;AAAAQ,iBAAAC,CAAAA,QAAA;AAAA,gBAAAC,MAPR,GAAG7C,SAAAA,EAAWG,GAAG,MAAI2C,OACpB,GAAG9C,SAAAA,EAAWI,IAAI,MAAI2C,OAErB,GAAGhD,MAAMa,SAAS,GAAG,MAAIoC,OAE3B,2GAA2GjD,MAAMkD,SAAS,EAAE;AAAEJ,oBAAAD,IAAA1B,KAAAgC,iBAAAb,MAAA,OAAAO,IAAA1B,IAAA2B,GAAA;AAAAC,qBAAAF,IAAArC,KAAA2C,iBAAAb,MAAA,QAAAO,IAAArC,IAAAuC,IAAA;AAAAC,qBAAAH,IAAAO,KAAAD,iBAAAb,MAAA,SAAAO,IAAAO,IAAAJ,IAAA;AAAAC,qBAAAJ,IAAAQ,KAAAC,UAAAhB,MAAAO,IAAAQ,IAAAJ,IAAA;AAAA,mBAAAJ;AAAAA,UAAA,GAAA;AAAA,YAAA1B,GAAAoC;AAAAA,YAAA/C,GAAA+C;AAAAA,YAAAH,GAAAG;AAAAA,YAAAF,GAAAE;AAAAA,UAAAA,CAAA;AAAA,iBAAAjB;AAAAA,QAAA;AAAA,MAAA,CAAA;AAAA,IAAA;AAAA,EAAA,CAAA;AAO/I;"}
|