@seed-ship/mcp-ui-solid 6.3.1 → 6.5.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/dist/components/GraphRenderer.cjs +30 -24
  3. package/dist/components/GraphRenderer.cjs.map +1 -1
  4. package/dist/components/GraphRenderer.d.ts.map +1 -1
  5. package/dist/components/GraphRenderer.js +30 -24
  6. package/dist/components/GraphRenderer.js.map +1 -1
  7. package/dist/components/PortalDropdownMenu.cjs +82 -0
  8. package/dist/components/PortalDropdownMenu.cjs.map +1 -0
  9. package/dist/components/PortalDropdownMenu.d.ts +56 -0
  10. package/dist/components/PortalDropdownMenu.d.ts.map +1 -0
  11. package/dist/components/PortalDropdownMenu.js +82 -0
  12. package/dist/components/PortalDropdownMenu.js.map +1 -0
  13. package/dist/components/UIResourceRenderer.cjs +297 -263
  14. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  15. package/dist/components/UIResourceRenderer.d.ts +20 -0
  16. package/dist/components/UIResourceRenderer.d.ts.map +1 -1
  17. package/dist/components/UIResourceRenderer.js +299 -265
  18. package/dist/components/UIResourceRenderer.js.map +1 -1
  19. package/dist/components/index.d.ts +2 -0
  20. package/dist/components/index.d.ts.map +1 -1
  21. package/dist/components.cjs +2 -0
  22. package/dist/components.cjs.map +1 -1
  23. package/dist/components.d.cts +2 -0
  24. package/dist/components.d.ts +2 -0
  25. package/dist/components.js +2 -0
  26. package/dist/components.js.map +1 -1
  27. package/dist/index.cjs +6 -0
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +5 -0
  30. package/dist/index.d.ts +5 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +6 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/utils/duplicate-mount-registry.cjs +27 -0
  35. package/dist/utils/duplicate-mount-registry.cjs.map +1 -0
  36. package/dist/utils/duplicate-mount-registry.d.ts +84 -0
  37. package/dist/utils/duplicate-mount-registry.d.ts.map +1 -0
  38. package/dist/utils/duplicate-mount-registry.js +27 -0
  39. package/dist/utils/duplicate-mount-registry.js.map +1 -0
  40. package/dist/utils/stable-key.cjs +41 -0
  41. package/dist/utils/stable-key.cjs.map +1 -0
  42. package/dist/utils/stable-key.d.ts +33 -0
  43. package/dist/utils/stable-key.d.ts.map +1 -0
  44. package/dist/utils/stable-key.js +41 -0
  45. package/dist/utils/stable-key.js.map +1 -0
  46. package/package.json +1 -1
  47. package/src/components/GraphRenderer.tsx +29 -20
  48. package/src/components/PortalDropdownMenu.test.tsx +113 -0
  49. package/src/components/PortalDropdownMenu.tsx +130 -0
  50. package/src/components/UIResourceRenderer.identity.test.tsx +161 -0
  51. package/src/components/UIResourceRenderer.tsx +85 -15
  52. package/src/components/index.ts +4 -0
  53. package/src/index.ts +10 -0
  54. package/src/utils/duplicate-mount-registry.test.ts +82 -0
  55. package/src/utils/duplicate-mount-registry.ts +113 -0
  56. package/src/utils/stable-key.test.ts +96 -0
  57. package/src/utils/stable-key.ts +91 -0
  58. package/tsconfig.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"GraphRenderer.js","sources":["../../src/components/GraphRenderer.tsx"],"sourcesContent":["/**\n * GraphRenderer (v6.0.0) — generic node-link visualization powered by\n * `@antv/g6 ^5` (peer-optional). Same lazy-load pattern as\n * `ChartJSRenderer` and `MapRenderer` : the heavy lib is dynamically\n * imported only on first mount, and apps that don't install the peer\n * see an informative fallback instead of a crash.\n *\n * Spec : `@seed-ship/mcp-ui-spec@5.0.4` exports `GraphComponentParamsSchema`,\n * `GraphNode`, `GraphEdge`, `GraphLayoutName`, `GraphLayout`,\n * `GraphComponentParams` — the shape consumed here. Domain semantics\n * (`weight` etc.) are opaque to this renderer ; consumers decide what\n * the values mean.\n *\n * Copy + export : the renderer ships with `<ExpandableWrapper>` (default\n * copy = JSON of `{nodes, edges}`) plus a 3-format export menu — **PNG**\n * (visual snapshot via the underlying canvas/SVG), **Mermaid** (markdown\n * / GitHub-renderable `flowchart` syntax), **JSON** (raw reimportable\n * data). All three are computed lazily on click.\n */\n\nimport { Component, createSignal, onCleanup, onMount, Show, For } from 'solid-js'\nimport type { UIComponent } from '../types'\nimport type { GraphComponentParams, GraphLayout, GraphNode, GraphEdge } from '@seed-ship/mcp-ui-spec'\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper'\n\n// Module-scoped lazy import promise — first call triggers the dynamic\n// import, subsequent calls reuse the resolved module.\nlet g6ModulePromise: Promise<typeof import('@antv/g6')> | undefined\n\n/**\n * Whether the `@antv/g6` peer dependency is installed and importable.\n * Resolves to `true` when the lib is available, `false` otherwise.\n *\n * Mirrors `isChartJSAvailable()` from `ChartJSRenderer`.\n */\nexport async function isG6Available(): Promise<boolean> {\n try {\n if (!g6ModulePromise) {\n g6ModulePromise = import('@antv/g6')\n }\n await g6ModulePromise\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Resolve the spec layout shorthand or object form into the config object\n * G6 v5 expects. When `layout` is omitted, picks `'force'` if edges are\n * present (universal default) or `'circular'` otherwise.\n */\nfunction resolveLayout(params: GraphComponentParams): { type: string; [key: string]: unknown } {\n const layout: GraphLayout | undefined = params.layout\n if (layout === undefined) {\n const hasEdges = (params.edges?.length ?? 0) > 0\n return { type: hasEdges ? 'force' : 'circular' }\n }\n if (typeof layout === 'string') {\n return { type: layout }\n }\n // Object form: spread the passthrough options alongside `type`.\n return { type: layout.type, ...(layout.options ?? {}) }\n}\n\n/**\n * Build the G6 v5 `behaviors` array from the params interactivity flags.\n * Defaults : drag-canvas + zoom-canvas + drag-element + click-select.\n * Any flag set to `false` opts out.\n */\nfunction resolveBehaviors(params: GraphComponentParams): string[] {\n const behaviors: string[] = []\n if (params.enableDrag !== false) behaviors.push('drag-element')\n if (params.enableZoom !== false) {\n behaviors.push('zoom-canvas', 'drag-canvas')\n }\n if (params.enableSelect !== false) behaviors.push('click-select')\n return behaviors\n}\n\n/**\n * Pick a sensible Mermaid `flowchart` direction from the resolved layout.\n * `dagre` / `tree` / `mindmap` are top-down hierarchies → TD ; everything\n * else (force, concentric, circular, grid) → LR (default mermaid).\n */\nfunction mermaidDirection(layoutType: string): 'TD' | 'LR' {\n return layoutType === 'dagre' || layoutType === 'tree' || layoutType === 'mindmap' ? 'TD' : 'LR'\n}\n\n/**\n * Sanitize a string for use inside a Mermaid node label. Mermaid breaks\n * on raw quotes / brackets / pipes ; we strip the worst offenders.\n */\nfunction mermaidLabel(s: string): string {\n return s.replace(/[\"[\\]|]/g, '').replace(/\\s+/g, ' ').trim()\n}\n\n/**\n * Convert the graph data to Mermaid `flowchart` syntax. The edge label\n * carries the optional `weight` prefix when present (e.g. `|3| label`).\n */\nfunction toMermaid(params: GraphComponentParams): string {\n const layoutType = resolveLayout(params).type\n const dir = mermaidDirection(layoutType)\n const lines: string[] = [`flowchart ${dir}`]\n for (const n of params.nodes) {\n const label = mermaidLabel(n.label ?? n.id)\n lines.push(` ${n.id}[\"${label}\"]`)\n }\n for (const e of params.edges ?? []) {\n const labelParts: string[] = []\n if (e.weight !== undefined) labelParts.push(String(e.weight))\n if (e.label) labelParts.push(mermaidLabel(e.label))\n const labelText = labelParts.join(' · ')\n if (labelText) {\n lines.push(` ${e.source} -->|${labelText}| ${e.target}`)\n } else {\n lines.push(` ${e.source} --> ${e.target}`)\n }\n }\n return lines.join('\\n')\n}\n\nfunction toJSON(params: GraphComponentParams): string {\n return JSON.stringify({ nodes: params.nodes, edges: params.edges ?? [] }, null, 2)\n}\n\nfunction downloadBlob(content: string | Blob, filename: string, mimeType?: string): void {\n const blob = typeof content === 'string' ? new Blob([content], { type: mimeType ?? 'text/plain' }) : content\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n document.body.appendChild(a)\n a.click()\n document.body.removeChild(a)\n URL.revokeObjectURL(url)\n}\n\nexport interface GraphRendererProps {\n component: UIComponent\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible'\n}\n\nexport const GraphRenderer: Component<GraphRendererProps> = (props) => {\n const params = () => props.component.params as GraphComponentParams\n const isExpanded = useExpanded()\n const [available, setAvailable] = createSignal<boolean | null>(null)\n const [error, setError] = createSignal<string | undefined>()\n const [exportMenuOpen, setExportMenuOpen] = createSignal(false)\n let containerRef: HTMLDivElement | undefined\n // Loosely typed because G6 is a peer-optional — we don't pull its\n // types into the bundle just to type a transient local handle.\n let graphInstance: any | undefined\n\n onMount(async () => {\n const g6Available = await isG6Available()\n setAvailable(g6Available)\n if (!g6Available || !containerRef) return\n\n try {\n const { Graph } = await g6ModulePromise!\n const p = params()\n const config: Record<string, unknown> = {\n container: containerRef,\n data: { nodes: p.nodes, edges: p.edges ?? [] },\n layout: resolveLayout(p),\n behaviors: resolveBehaviors(p),\n renderer: p.rendererPref === 'svg' ? 'svg' : 'canvas',\n }\n if (p.fitView !== false) {\n config.autoFit = 'view'\n }\n if (p.tooltip !== false) {\n // Built-in tooltip plugin — shows label + a compact dump of\n // node.data on hover. Consumers can opt out with `tooltip: false`.\n config.plugins = [\n {\n type: 'tooltip',\n getContent: (_evt: unknown, items: any[]) => {\n const item = items?.[0]\n if (!item) return ''\n const label = item.label ?? item.id ?? ''\n const data = item.data ? JSON.stringify(item.data) : ''\n return `<div style=\"padding:4px 8px\"><strong>${escapeHtml(String(label))}</strong>${\n data ? `<br><span style=\"font-size:11px;opacity:0.7\">${escapeHtml(data)}</span>` : ''\n }</div>`\n },\n },\n ]\n }\n graphInstance = new (Graph as any)(config)\n await graphInstance.render()\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to render graph')\n }\n })\n\n onCleanup(() => {\n try {\n graphInstance?.destroy()\n } catch {\n // G6 destroy can throw on already-destroyed instances or partial\n // init failures — silent because the component is unmounting anyway.\n }\n graphInstance = undefined\n })\n\n // ─── Export handlers ────────────────────────────────────────────────\n const handleExportJSON = () => {\n downloadBlob(toJSON(params()), `${graphFilenameStem(params())}.json`, 'application/json')\n setExportMenuOpen(false)\n }\n\n const handleExportMermaid = () => {\n downloadBlob(toMermaid(params()), `${graphFilenameStem(params())}.mmd`, 'text/plain')\n setExportMenuOpen(false)\n }\n\n const handleExportPNG = async () => {\n if (!graphInstance) return\n try {\n // G6 v5 exposes `toDataURL()` on the graph instance.\n const dataUrl: string = await graphInstance.toDataURL?.('image/png')\n if (!dataUrl) {\n // Fallback: try to grab the underlying canvas directly.\n const canvas = containerRef?.querySelector('canvas')\n if (canvas) {\n const url = (canvas as HTMLCanvasElement).toDataURL('image/png')\n await downloadDataUrl(url, `${graphFilenameStem(params())}.png`)\n } else {\n setError('PNG export not supported in current renderer mode')\n }\n } else {\n await downloadDataUrl(dataUrl, `${graphFilenameStem(params())}.png`)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'PNG export failed')\n }\n setExportMenuOpen(false)\n }\n\n return (\n <Show\n when={available() === true}\n fallback={\n <Show\n when={available() === false}\n fallback={\n // Loading skeleton while we determine peer availability\n <div class=\"w-full p-4 bg-gray-50 dark:bg-gray-800/40 border border-gray-200 dark:border-gray-700 rounded-lg animate-pulse\">\n <div class=\"h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded mb-2\" />\n <div class=\"h-3 w-48 bg-gray-200 dark:bg-gray-700 rounded\" />\n </div>\n }\n >\n <div class=\"w-full p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg\">\n <p class=\"text-sm font-medium text-yellow-900 dark:text-yellow-100\">\n Graph rendering unavailable\n </p>\n <p class=\"text-xs text-yellow-700 dark:text-yellow-300 mt-1\">\n Install <code>@antv/g6</code> peer dependency to render <code>type: \"graph\"</code> components.\n </p>\n </div>\n </Show>\n }\n >\n <ExpandableWrapper\n title={params().title ?? 'Graph'}\n copyData={toJSON(params())}\n copyLabel=\"Copy graph (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div class={`relative w-full ${params().className ?? ''} ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}>\n {/* Export menu — top-right, mirrors TableRenderer's pattern */}\n <div class=\"absolute right-2 top-2 z-10\">\n <button\n type=\"button\"\n onClick={() => setExportMenuOpen((v) => !v)}\n class=\"px-2 py-1 text-xs bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300\"\n title=\"Export graph\"\n aria-label=\"Export graph\"\n aria-expanded={exportMenuOpen()}\n >\n Export ▾\n </button>\n <Show when={exportMenuOpen()}>\n <div class=\"absolute right-0 mt-1 w-44 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded shadow-lg text-xs\">\n <For each={[\n { label: 'Download PNG', onClick: handleExportPNG, hint: 'visual snapshot' },\n { label: 'Download Mermaid', onClick: handleExportMermaid, hint: 'markdown / GitHub' },\n { label: 'Download JSON', onClick: handleExportJSON, hint: 'raw data' },\n ]}>\n {(item) => (\n <button\n type=\"button\"\n onClick={item.onClick}\n class=\"w-full text-left px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-gray-700 last:border-b-0\"\n >\n <div class=\"font-medium\">{item.label}</div>\n <div class=\"text-[10px] text-gray-500 dark:text-gray-400\">{item.hint}</div>\n </button>\n )}\n </For>\n </div>\n </Show>\n </div>\n\n <div\n ref={containerRef}\n class={`bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden ${\n isExpanded() ? 'flex-1 min-h-0' : ''\n }`}\n style={\n isExpanded()\n ? `height: 100%; width: ${params().width ?? '100%'};`\n : `height: ${params().height ?? '400px'}; width: ${params().width ?? '100%'};`\n }\n />\n <Show when={error()}>\n <p class=\"text-xs text-red-600 dark:text-red-400 mt-1\">Render error: {error()}</p>\n </Show>\n </div>\n </ExpandableWrapper>\n </Show>\n )\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────\n\nfunction graphFilenameStem(params: GraphComponentParams): string {\n const base = (params.title ?? 'graph').replace(/[^a-z0-9-_]+/gi, '-').replace(/^-+|-+$/g, '')\n return base || 'graph'\n}\n\nasync function downloadDataUrl(dataUrl: string, filename: string): Promise<void> {\n const res = await fetch(dataUrl)\n const blob = await res.blob()\n downloadBlob(blob, filename)\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => {\n switch (c) {\n case '&': return '&amp;'\n case '<': return '&lt;'\n case '>': return '&gt;'\n case '\"': return '&quot;'\n case \"'\": return '&#39;'\n default: return c\n }\n })\n}\n\n// Re-export for tests + consumers that want to compose their own export menu\nexport { toMermaid as graphToMermaid, toJSON as graphToJSON }\n"],"names":["g6ModulePromise","isG6Available","resolveLayout","params","layout","undefined","hasEdges","edges","length","type","options","resolveBehaviors","behaviors","enableDrag","push","enableZoom","enableSelect","mermaidDirection","layoutType","mermaidLabel","s","replace","trim","toMermaid","dir","lines","n","nodes","label","id","e","labelParts","weight","String","labelText","join","source","target","toJSON","JSON","stringify","downloadBlob","content","filename","mimeType","blob","Blob","url","URL","createObjectURL","a","document","createElement","href","download","body","appendChild","click","removeChild","revokeObjectURL","GraphRenderer","props","component","isExpanded","useExpanded","available","setAvailable","createSignal","error","setError","exportMenuOpen","setExportMenuOpen","containerRef","graphInstance","onMount","g6Available","Graph","p","config","container","data","renderer","rendererPref","fitView","autoFit","tooltip","plugins","getContent","_evt","items","item","escapeHtml","render","err","Error","message","onCleanup","destroy","handleExportJSON","graphFilenameStem","handleExportMermaid","handleExportPNG","dataUrl","toDataURL","canvas","querySelector","downloadDataUrl","_$createComponent","Show","when","fallback","_$getNextElement","_tmpl$5","children","_tmpl$4","ExpandableWrapper","title","copyData","copyLabel","toolbarVariant","_el$","_tmpl$3","_el$2","firstChild","_el$3","_el$5","nextSibling","_el$6","_co$","_$getNextMarker","_el$7","_el$10","_el$11","_co$3","$$click","v","_$insert","_el$4","_tmpl$","For","each","onClick","hint","_el$14","_tmpl$6","_el$15","_el$16","_$addEventListener","_$runHydrationEvents","_ref$","_$use","_el$8","_tmpl$2","_el$9","_el$0","_el$1","_co$2","_$effect","_p$","_v$","className","_v$2","_v$3","_v$4","width","height","_$className","t","_$setAttribute","o","_$style","base","res","fetch","c","_$delegateEvents"],"mappings":"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"}
1
+ {"version":3,"file":"GraphRenderer.js","sources":["../../src/components/GraphRenderer.tsx"],"sourcesContent":["/**\n * GraphRenderer (v6.0.0) — generic node-link visualization powered by\n * `@antv/g6 ^5` (peer-optional). Same lazy-load pattern as\n * `ChartJSRenderer` and `MapRenderer` : the heavy lib is dynamically\n * imported only on first mount, and apps that don't install the peer\n * see an informative fallback instead of a crash.\n *\n * Spec : `@seed-ship/mcp-ui-spec@5.0.4` exports `GraphComponentParamsSchema`,\n * `GraphNode`, `GraphEdge`, `GraphLayoutName`, `GraphLayout`,\n * `GraphComponentParams` — the shape consumed here. Domain semantics\n * (`weight` etc.) are opaque to this renderer ; consumers decide what\n * the values mean.\n *\n * Copy + export : the renderer ships with `<ExpandableWrapper>` (default\n * copy = JSON of `{nodes, edges}`) plus a 3-format export menu — **PNG**\n * (visual snapshot via the underlying canvas/SVG), **Mermaid** (markdown\n * / GitHub-renderable `flowchart` syntax), **JSON** (raw reimportable\n * data). All three are computed lazily on click.\n */\n\nimport { Component, createSignal, onCleanup, onMount, Show, For } from 'solid-js'\nimport type { UIComponent } from '../types'\nimport type { GraphComponentParams, GraphLayout, GraphNode, GraphEdge } from '@seed-ship/mcp-ui-spec'\nimport { ExpandableWrapper, useExpanded } from './ExpandableWrapper'\nimport { PortalDropdownMenu } from './PortalDropdownMenu'\n\n// Module-scoped lazy import promise — first call triggers the dynamic\n// import, subsequent calls reuse the resolved module.\nlet g6ModulePromise: Promise<typeof import('@antv/g6')> | undefined\n\n/**\n * Whether the `@antv/g6` peer dependency is installed and importable.\n * Resolves to `true` when the lib is available, `false` otherwise.\n *\n * Mirrors `isChartJSAvailable()` from `ChartJSRenderer`.\n */\nexport async function isG6Available(): Promise<boolean> {\n try {\n if (!g6ModulePromise) {\n g6ModulePromise = import('@antv/g6')\n }\n await g6ModulePromise\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Resolve the spec layout shorthand or object form into the config object\n * G6 v5 expects. When `layout` is omitted, picks `'force'` if edges are\n * present (universal default) or `'circular'` otherwise.\n */\nfunction resolveLayout(params: GraphComponentParams): { type: string; [key: string]: unknown } {\n const layout: GraphLayout | undefined = params.layout\n if (layout === undefined) {\n const hasEdges = (params.edges?.length ?? 0) > 0\n return { type: hasEdges ? 'force' : 'circular' }\n }\n if (typeof layout === 'string') {\n return { type: layout }\n }\n // Object form: spread the passthrough options alongside `type`.\n return { type: layout.type, ...(layout.options ?? {}) }\n}\n\n/**\n * Build the G6 v5 `behaviors` array from the params interactivity flags.\n * Defaults : drag-canvas + zoom-canvas + drag-element + click-select.\n * Any flag set to `false` opts out.\n */\nfunction resolveBehaviors(params: GraphComponentParams): string[] {\n const behaviors: string[] = []\n if (params.enableDrag !== false) behaviors.push('drag-element')\n if (params.enableZoom !== false) {\n behaviors.push('zoom-canvas', 'drag-canvas')\n }\n if (params.enableSelect !== false) behaviors.push('click-select')\n return behaviors\n}\n\n/**\n * Pick a sensible Mermaid `flowchart` direction from the resolved layout.\n * `dagre` / `tree` / `mindmap` are top-down hierarchies → TD ; everything\n * else (force, concentric, circular, grid) → LR (default mermaid).\n */\nfunction mermaidDirection(layoutType: string): 'TD' | 'LR' {\n return layoutType === 'dagre' || layoutType === 'tree' || layoutType === 'mindmap' ? 'TD' : 'LR'\n}\n\n/**\n * Sanitize a string for use inside a Mermaid node label. Mermaid breaks\n * on raw quotes / brackets / pipes ; we strip the worst offenders.\n */\nfunction mermaidLabel(s: string): string {\n return s.replace(/[\"[\\]|]/g, '').replace(/\\s+/g, ' ').trim()\n}\n\n/**\n * Convert the graph data to Mermaid `flowchart` syntax. The edge label\n * carries the optional `weight` prefix when present (e.g. `|3| label`).\n */\nfunction toMermaid(params: GraphComponentParams): string {\n const layoutType = resolveLayout(params).type\n const dir = mermaidDirection(layoutType)\n const lines: string[] = [`flowchart ${dir}`]\n for (const n of params.nodes) {\n const label = mermaidLabel(n.label ?? n.id)\n lines.push(` ${n.id}[\"${label}\"]`)\n }\n for (const e of params.edges ?? []) {\n const labelParts: string[] = []\n if (e.weight !== undefined) labelParts.push(String(e.weight))\n if (e.label) labelParts.push(mermaidLabel(e.label))\n const labelText = labelParts.join(' · ')\n if (labelText) {\n lines.push(` ${e.source} -->|${labelText}| ${e.target}`)\n } else {\n lines.push(` ${e.source} --> ${e.target}`)\n }\n }\n return lines.join('\\n')\n}\n\nfunction toJSON(params: GraphComponentParams): string {\n return JSON.stringify({ nodes: params.nodes, edges: params.edges ?? [] }, null, 2)\n}\n\nfunction downloadBlob(content: string | Blob, filename: string, mimeType?: string): void {\n const blob = typeof content === 'string' ? new Blob([content], { type: mimeType ?? 'text/plain' }) : content\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n document.body.appendChild(a)\n a.click()\n document.body.removeChild(a)\n URL.revokeObjectURL(url)\n}\n\nexport interface GraphRendererProps {\n component: UIComponent\n /**\n * Forwarded to the underlying `<ExpandableWrapper>` (v6.3.1).\n * @see ExpandableWrapperProps.toolbarVariant\n */\n toolbarVariant?: 'hover' | 'always-visible'\n}\n\nexport const GraphRenderer: Component<GraphRendererProps> = (props) => {\n const params = () => props.component.params as GraphComponentParams\n const isExpanded = useExpanded()\n const [available, setAvailable] = createSignal<boolean | null>(null)\n const [error, setError] = createSignal<string | undefined>()\n const [exportMenuOpen, setExportMenuOpen] = createSignal(false)\n let containerRef: HTMLDivElement | undefined\n // v6.4.0 — trigger ref consumed by <PortalDropdownMenu> for positioning\n let exportTriggerRef: HTMLButtonElement | undefined\n // Loosely typed because G6 is a peer-optional — we don't pull its\n // types into the bundle just to type a transient local handle.\n let graphInstance: any | undefined\n\n onMount(async () => {\n const g6Available = await isG6Available()\n setAvailable(g6Available)\n if (!g6Available || !containerRef) return\n\n try {\n const { Graph } = await g6ModulePromise!\n const p = params()\n const config: Record<string, unknown> = {\n container: containerRef,\n data: { nodes: p.nodes, edges: p.edges ?? [] },\n layout: resolveLayout(p),\n behaviors: resolveBehaviors(p),\n renderer: p.rendererPref === 'svg' ? 'svg' : 'canvas',\n }\n if (p.fitView !== false) {\n config.autoFit = 'view'\n }\n if (p.tooltip !== false) {\n // Built-in tooltip plugin — shows label + a compact dump of\n // node.data on hover. Consumers can opt out with `tooltip: false`.\n config.plugins = [\n {\n type: 'tooltip',\n getContent: (_evt: unknown, items: any[]) => {\n const item = items?.[0]\n if (!item) return ''\n const label = item.label ?? item.id ?? ''\n const data = item.data ? JSON.stringify(item.data) : ''\n return `<div style=\"padding:4px 8px\"><strong>${escapeHtml(String(label))}</strong>${\n data ? `<br><span style=\"font-size:11px;opacity:0.7\">${escapeHtml(data)}</span>` : ''\n }</div>`\n },\n },\n ]\n }\n graphInstance = new (Graph as any)(config)\n await graphInstance.render()\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to render graph')\n }\n })\n\n onCleanup(() => {\n try {\n graphInstance?.destroy()\n } catch {\n // G6 destroy can throw on already-destroyed instances or partial\n // init failures — silent because the component is unmounting anyway.\n }\n graphInstance = undefined\n })\n\n // ─── Export handlers ────────────────────────────────────────────────\n const handleExportJSON = () => {\n downloadBlob(toJSON(params()), `${graphFilenameStem(params())}.json`, 'application/json')\n setExportMenuOpen(false)\n }\n\n const handleExportMermaid = () => {\n downloadBlob(toMermaid(params()), `${graphFilenameStem(params())}.mmd`, 'text/plain')\n setExportMenuOpen(false)\n }\n\n const handleExportPNG = async () => {\n if (!graphInstance) return\n try {\n // G6 v5 exposes `toDataURL()` on the graph instance.\n const dataUrl: string = await graphInstance.toDataURL?.('image/png')\n if (!dataUrl) {\n // Fallback: try to grab the underlying canvas directly.\n const canvas = containerRef?.querySelector('canvas')\n if (canvas) {\n const url = (canvas as HTMLCanvasElement).toDataURL('image/png')\n await downloadDataUrl(url, `${graphFilenameStem(params())}.png`)\n } else {\n setError('PNG export not supported in current renderer mode')\n }\n } else {\n await downloadDataUrl(dataUrl, `${graphFilenameStem(params())}.png`)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'PNG export failed')\n }\n setExportMenuOpen(false)\n }\n\n return (\n <Show\n when={available() === true}\n fallback={\n <Show\n when={available() === false}\n fallback={\n // Loading skeleton while we determine peer availability\n <div class=\"w-full p-4 bg-gray-50 dark:bg-gray-800/40 border border-gray-200 dark:border-gray-700 rounded-lg animate-pulse\">\n <div class=\"h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded mb-2\" />\n <div class=\"h-3 w-48 bg-gray-200 dark:bg-gray-700 rounded\" />\n </div>\n }\n >\n <div class=\"w-full p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg\">\n <p class=\"text-sm font-medium text-yellow-900 dark:text-yellow-100\">\n Graph rendering unavailable\n </p>\n <p class=\"text-xs text-yellow-700 dark:text-yellow-300 mt-1\">\n Install <code>@antv/g6</code> peer dependency to render <code>type: \"graph\"</code> components.\n </p>\n </div>\n </Show>\n }\n >\n <ExpandableWrapper\n title={params().title ?? 'Graph'}\n copyData={toJSON(params())}\n copyLabel=\"Copy graph (JSON)\"\n toolbarVariant={props.toolbarVariant}\n >\n <div class={`relative w-full ${params().className ?? ''} ${\n isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''\n }`}>\n {/* Export menu — top-right, mirrors TableRenderer's pattern */}\n <div class=\"absolute right-2 top-2 z-10\">\n <button\n ref={exportTriggerRef}\n type=\"button\"\n onClick={() => setExportMenuOpen((v) => !v)}\n class=\"px-2 py-1 text-xs bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300\"\n title=\"Export graph\"\n aria-label=\"Export graph\"\n aria-haspopup=\"menu\"\n aria-expanded={exportMenuOpen()}\n >\n Export ▾\n </button>\n <PortalDropdownMenu\n open={exportMenuOpen()}\n onClose={() => setExportMenuOpen(false)}\n trigger={exportTriggerRef}\n width={176}\n class=\"text-xs\"\n >\n <For each={[\n { label: 'Download PNG', onClick: handleExportPNG, hint: 'visual snapshot' },\n { label: 'Download Mermaid', onClick: handleExportMermaid, hint: 'markdown / GitHub' },\n { label: 'Download JSON', onClick: handleExportJSON, hint: 'raw data' },\n ]}>\n {(item) => (\n <button\n type=\"button\"\n onClick={item.onClick}\n class=\"w-full text-left px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-b border-gray-100 dark:border-gray-700 last:border-b-0\"\n >\n <div class=\"font-medium\">{item.label}</div>\n <div class=\"text-[10px] text-gray-500 dark:text-gray-400\">{item.hint}</div>\n </button>\n )}\n </For>\n </PortalDropdownMenu>\n </div>\n\n <div\n ref={containerRef}\n class={`bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden ${\n isExpanded() ? 'flex-1 min-h-0' : ''\n }`}\n style={\n isExpanded()\n ? `height: 100%; width: ${params().width ?? '100%'};`\n : `height: ${params().height ?? '400px'}; width: ${params().width ?? '100%'};`\n }\n />\n <Show when={error()}>\n <p class=\"text-xs text-red-600 dark:text-red-400 mt-1\">Render error: {error()}</p>\n </Show>\n </div>\n </ExpandableWrapper>\n </Show>\n )\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────\n\nfunction graphFilenameStem(params: GraphComponentParams): string {\n const base = (params.title ?? 'graph').replace(/[^a-z0-9-_]+/gi, '-').replace(/^-+|-+$/g, '')\n return base || 'graph'\n}\n\nasync function downloadDataUrl(dataUrl: string, filename: string): Promise<void> {\n const res = await fetch(dataUrl)\n const blob = await res.blob()\n downloadBlob(blob, filename)\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => {\n switch (c) {\n case '&': return '&amp;'\n case '<': return '&lt;'\n case '>': return '&gt;'\n case '\"': return '&quot;'\n case \"'\": return '&#39;'\n default: return c\n }\n })\n}\n\n// Re-export for tests + consumers that want to compose their own export menu\nexport { toMermaid as graphToMermaid, toJSON as graphToJSON }\n"],"names":["g6ModulePromise","isG6Available","resolveLayout","params","layout","undefined","hasEdges","edges","length","type","options","resolveBehaviors","behaviors","enableDrag","push","enableZoom","enableSelect","mermaidDirection","layoutType","mermaidLabel","s","replace","trim","toMermaid","dir","lines","n","nodes","label","id","e","labelParts","weight","String","labelText","join","source","target","toJSON","JSON","stringify","downloadBlob","content","filename","mimeType","blob","Blob","url","URL","createObjectURL","a","document","createElement","href","download","body","appendChild","click","removeChild","revokeObjectURL","GraphRenderer","props","component","isExpanded","useExpanded","available","setAvailable","createSignal","error","setError","exportMenuOpen","setExportMenuOpen","containerRef","exportTriggerRef","graphInstance","onMount","g6Available","Graph","p","config","container","data","renderer","rendererPref","fitView","autoFit","tooltip","plugins","getContent","_evt","items","item","escapeHtml","render","err","Error","message","onCleanup","destroy","handleExportJSON","graphFilenameStem","handleExportMermaid","handleExportPNG","dataUrl","toDataURL","canvas","querySelector","downloadDataUrl","_$createComponent","Show","when","fallback","_$getNextElement","_tmpl$4","children","_tmpl$3","ExpandableWrapper","title","copyData","copyLabel","toolbarVariant","_el$","_tmpl$2","_el$2","firstChild","_el$3","_el$4","nextSibling","_el$5","_co$","_$getNextMarker","_el$6","_el$1","_el$10","_co$3","$$click","v","_ref$","_$use","_$insert","PortalDropdownMenu","open","onClose","trigger","width","For","each","onClick","hint","_el$13","_tmpl$5","_el$14","_el$15","_$addEventListener","_$runHydrationEvents","_ref$2","_el$7","_tmpl$","_el$8","_el$9","_el$0","_co$2","_$effect","_p$","_v$","className","_v$2","_v$3","_v$4","height","_$className","t","_$setAttribute","o","_$style","base","res","fetch","c","_$delegateEvents"],"mappings":"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"}
@@ -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":";;;;;AA+DO,MAAMA,qBAA0DC,CAAAA,UAAU;AAC/E,QAAM,CAACC,UAAUC,WAAW,IAAIC,qBAAa;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,UAAAA,aAAa,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,YAAAA,UAAU,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,IAAAA,gBACGC,QAAAA,MAAI;AAAA,IAAA,IAACC,OAAI;AAAA,aAAEnC,MAAMiB;AAAAA,IAAI;AAAA,IAAA,IAAAmB,WAAA;AAAA,aAAAH,IAAAA,gBACnBI,IAAAA,QAAM;AAAA,QAAA,IAAAD,WAAA;AAAA,cAAAE,OAAAC,IAAAA,eAAAC,MAAA;AAAA,cAAAC,QAEEnC;AAAO,iBAAAmC,UAAA,aAAAC,IAAAA,IAAAD,OAAAH,IAAA,IAAPhC,UAAOgC;AAAAK,cAAAA,OAAAL,MAAA,MAWXtC,MAAMoC,QAAQ;AAAAQ,cAAAA,OAAAC,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,IAAAA,iBAAAb,MAAA,OAAAO,IAAA1B,IAAA2B,GAAA;AAAAC,qBAAAF,IAAArC,KAAA2C,IAAAA,iBAAAb,MAAA,QAAAO,IAAArC,IAAAuC,IAAA;AAAAC,qBAAAH,IAAAO,KAAAD,IAAAA,iBAAAb,MAAA,SAAAO,IAAAO,IAAAJ,IAAA;AAAAC,qBAAAJ,IAAAQ,KAAAC,IAAAA,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;;"}
@@ -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;"}