@nimblebrain/synapse
Advanced tools
+55
-29
@@ -310,17 +310,19 @@ 'use strict'; | ||
| var RULES3 = ` | ||
| .nb-drawer-scrim { | ||
| position: fixed; inset: 0; z-index: 1000; | ||
| display: flex; background: rgba(0, 0, 0, 0.32); | ||
| animation: nb-drawer-fade 200ms ease; | ||
| } | ||
| .nb-drawer-scrim--right { justify-content: flex-end; } | ||
| .nb-drawer-scrim--left { justify-content: flex-start; } | ||
| .nb-drawer-scrim--bottom { align-items: flex-end; } | ||
| .nb-drawer { | ||
| margin: 0; padding: 0; border: none; max-width: 92%; | ||
| height: 100%; max-height: 100%; display: flex; flex-direction: column; | ||
| outline: none; | ||
| } | ||
| .nb-drawer--right { margin-left: auto; } | ||
| .nb-drawer--left { margin-right: auto; } | ||
| .nb-drawer--bottom { | ||
| margin-top: auto; width: 100%; max-width: 100%; | ||
| height: auto; max-height: 92%; | ||
| } | ||
| .nb-drawer--right[open] { animation: nb-drawer-in-right 240ms cubic-bezier(0.2, 0, 0, 1); } | ||
| .nb-drawer--left[open] { animation: nb-drawer-in-left 240ms cubic-bezier(0.2, 0, 0, 1); } | ||
| .nb-drawer--bottom[open] { animation: nb-drawer-in-bottom 240ms cubic-bezier(0.2, 0, 0, 1); } | ||
| .nb-drawer::backdrop { background: rgba(0, 0, 0, 0.32); } | ||
| .nb-drawer[open]::backdrop { animation: nb-drawer-fade 200ms ease; } | ||
| .nb-drawer--bottom { width: 100%; max-width: 100%; height: auto; max-height: 92%; } | ||
| .nb-drawer--right { animation: nb-drawer-in-right 240ms cubic-bezier(0.2, 0, 0, 1); } | ||
| .nb-drawer--left { animation: nb-drawer-in-left 240ms cubic-bezier(0.2, 0, 0, 1); } | ||
| .nb-drawer--bottom { animation: nb-drawer-in-bottom 240ms cubic-bezier(0.2, 0, 0, 1); } | ||
| @keyframes nb-drawer-in-right { from { transform: translateX(100%); } } | ||
@@ -347,11 +349,30 @@ @keyframes nb-drawer-in-left { from { transform: translateX(-100%); } } | ||
| chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID3, RULES3); | ||
| const dialogRef = react.useRef(null); | ||
| const panelRef = react.useRef(null); | ||
| const labelId = react.useId(); | ||
| const [hasTitle, setHasTitle] = react.useState(false); | ||
| react.useEffect(() => { | ||
| const dialog = dialogRef.current; | ||
| if (!dialog || typeof dialog.showModal !== "function") return; | ||
| if (open && !dialog.open) dialog.showModal(); | ||
| else if (!open && dialog.open) dialog.close(); | ||
| if (!open) return; | ||
| const onKey = (e) => { | ||
| if (e.key === "Escape") { | ||
| e.preventDefault(); | ||
| (onEscape ?? onClose)(); | ||
| } | ||
| }; | ||
| window.addEventListener("keydown", onKey); | ||
| return () => window.removeEventListener("keydown", onKey); | ||
| }, [open, onEscape, onClose]); | ||
| react.useEffect(() => { | ||
| if (!open) return; | ||
| const previouslyFocused = document.activeElement; | ||
| panelRef.current?.focus(); | ||
| const { body } = document; | ||
| const prevOverflow = body.style.overflow; | ||
| body.style.overflow = "hidden"; | ||
| return () => { | ||
| body.style.overflow = prevOverflow; | ||
| previouslyFocused?.focus?.(); | ||
| }; | ||
| }, [open]); | ||
| const ctxValue = react.useMemo(() => ({ labelId, setHasTitle }), [labelId]); | ||
| if (!open) return null; | ||
| const isBottom = side === "bottom"; | ||
@@ -363,22 +384,27 @@ const panelStyle = { | ||
| boxShadow: tokens.shadowLg, | ||
| outline: "none", | ||
| ...side === "right" ? { borderLeft: `${tokens.borderWidth} solid ${tokens.border}` } : side === "left" ? { borderRight: `${tokens.borderWidth} solid ${tokens.border}` } : { borderTop: `${tokens.borderWidth} solid ${tokens.border}` }, | ||
| ...style | ||
| }; | ||
| const ctxValue = react.useMemo(() => ({ labelId, setHasTitle }), [labelId]); | ||
| return /* @__PURE__ */ jsxRuntime.jsx(DrawerContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsxRuntime.jsx( | ||
| "dialog", | ||
| "div", | ||
| { | ||
| ref: dialogRef, | ||
| className: `nb-drawer nb-drawer--${side}`, | ||
| style: panelStyle, | ||
| "aria-labelledby": hasTitle ? labelId : void 0, | ||
| onCancel: (e) => { | ||
| e.preventDefault(); | ||
| (onEscape ?? onClose)(); | ||
| }, | ||
| className: `nb-drawer-scrim nb-drawer-scrim--${side}`, | ||
| onClick: (e) => { | ||
| if (e.target === dialogRef.current) onClose(); | ||
| if (e.target === e.currentTarget) onClose(); | ||
| }, | ||
| ...rest, | ||
| children | ||
| children: /* @__PURE__ */ jsxRuntime.jsx( | ||
| "div", | ||
| { | ||
| ref: panelRef, | ||
| role: "dialog", | ||
| "aria-modal": "true", | ||
| "aria-labelledby": hasTitle ? labelId : void 0, | ||
| tabIndex: -1, | ||
| className: `nb-drawer nb-drawer--${side}`, | ||
| style: panelStyle, | ||
| ...rest, | ||
| children | ||
| } | ||
| ) | ||
| } | ||
@@ -385,0 +411,0 @@ ) }); |
| import * as react_jsx_runtime from 'react/jsx-runtime'; | ||
| import { HTMLAttributes, ReactNode, ButtonHTMLAttributes, DialogHTMLAttributes, InputHTMLAttributes, RefObject, CSSProperties, ElementType } from 'react'; | ||
| import { HTMLAttributes, ReactNode, ButtonHTMLAttributes, InputHTMLAttributes, RefObject, CSSProperties, ElementType } from 'react'; | ||
@@ -47,5 +47,5 @@ interface AvatarProps extends Omit<HTMLAttributes<HTMLSpanElement>, "children"> { | ||
| type Side$1 = "left" | "right" | "bottom"; | ||
| interface DrawerProps extends Omit<DialogHTMLAttributes<HTMLDialogElement>, "title" | "onCancel" | "onClick"> { | ||
| interface DrawerProps extends Omit<HTMLAttributes<HTMLDivElement>, "onClick"> { | ||
| open: boolean; | ||
| /** Called by the backdrop and the Header close button. */ | ||
| /** Called by the scrim and the Header close button. */ | ||
| onClose: () => void; | ||
@@ -60,3 +60,3 @@ /** Called on Escape. Defaults to `onClose` — override to do something else | ||
| } | ||
| declare function DrawerRoot({ open, onClose, onEscape, side, width, style, children, ...rest }: DrawerProps): react_jsx_runtime.JSX.Element; | ||
| declare function DrawerRoot({ open, onClose, onEscape, side, width, style, children, ...rest }: DrawerProps): react_jsx_runtime.JSX.Element | null; | ||
| interface HeaderProps extends Omit<HTMLAttributes<HTMLElement>, "title"> { | ||
@@ -63,0 +63,0 @@ /** |
| import * as react_jsx_runtime from 'react/jsx-runtime'; | ||
| import { HTMLAttributes, ReactNode, ButtonHTMLAttributes, DialogHTMLAttributes, InputHTMLAttributes, RefObject, CSSProperties, ElementType } from 'react'; | ||
| import { HTMLAttributes, ReactNode, ButtonHTMLAttributes, InputHTMLAttributes, RefObject, CSSProperties, ElementType } from 'react'; | ||
@@ -47,5 +47,5 @@ interface AvatarProps extends Omit<HTMLAttributes<HTMLSpanElement>, "children"> { | ||
| type Side$1 = "left" | "right" | "bottom"; | ||
| interface DrawerProps extends Omit<DialogHTMLAttributes<HTMLDialogElement>, "title" | "onCancel" | "onClick"> { | ||
| interface DrawerProps extends Omit<HTMLAttributes<HTMLDivElement>, "onClick"> { | ||
| open: boolean; | ||
| /** Called by the backdrop and the Header close button. */ | ||
| /** Called by the scrim and the Header close button. */ | ||
| onClose: () => void; | ||
@@ -60,3 +60,3 @@ /** Called on Escape. Defaults to `onClose` — override to do something else | ||
| } | ||
| declare function DrawerRoot({ open, onClose, onEscape, side, width, style, children, ...rest }: DrawerProps): react_jsx_runtime.JSX.Element; | ||
| declare function DrawerRoot({ open, onClose, onEscape, side, width, style, children, ...rest }: DrawerProps): react_jsx_runtime.JSX.Element | null; | ||
| interface HeaderProps extends Omit<HTMLAttributes<HTMLElement>, "title"> { | ||
@@ -63,0 +63,0 @@ /** |
+55
-29
@@ -308,17 +308,19 @@ import { ensureStyle, injectBaseReset } from '../chunk-3HQGHPNC.js'; | ||
| var RULES3 = ` | ||
| .nb-drawer-scrim { | ||
| position: fixed; inset: 0; z-index: 1000; | ||
| display: flex; background: rgba(0, 0, 0, 0.32); | ||
| animation: nb-drawer-fade 200ms ease; | ||
| } | ||
| .nb-drawer-scrim--right { justify-content: flex-end; } | ||
| .nb-drawer-scrim--left { justify-content: flex-start; } | ||
| .nb-drawer-scrim--bottom { align-items: flex-end; } | ||
| .nb-drawer { | ||
| margin: 0; padding: 0; border: none; max-width: 92%; | ||
| height: 100%; max-height: 100%; display: flex; flex-direction: column; | ||
| outline: none; | ||
| } | ||
| .nb-drawer--right { margin-left: auto; } | ||
| .nb-drawer--left { margin-right: auto; } | ||
| .nb-drawer--bottom { | ||
| margin-top: auto; width: 100%; max-width: 100%; | ||
| height: auto; max-height: 92%; | ||
| } | ||
| .nb-drawer--right[open] { animation: nb-drawer-in-right 240ms cubic-bezier(0.2, 0, 0, 1); } | ||
| .nb-drawer--left[open] { animation: nb-drawer-in-left 240ms cubic-bezier(0.2, 0, 0, 1); } | ||
| .nb-drawer--bottom[open] { animation: nb-drawer-in-bottom 240ms cubic-bezier(0.2, 0, 0, 1); } | ||
| .nb-drawer::backdrop { background: rgba(0, 0, 0, 0.32); } | ||
| .nb-drawer[open]::backdrop { animation: nb-drawer-fade 200ms ease; } | ||
| .nb-drawer--bottom { width: 100%; max-width: 100%; height: auto; max-height: 92%; } | ||
| .nb-drawer--right { animation: nb-drawer-in-right 240ms cubic-bezier(0.2, 0, 0, 1); } | ||
| .nb-drawer--left { animation: nb-drawer-in-left 240ms cubic-bezier(0.2, 0, 0, 1); } | ||
| .nb-drawer--bottom { animation: nb-drawer-in-bottom 240ms cubic-bezier(0.2, 0, 0, 1); } | ||
| @keyframes nb-drawer-in-right { from { transform: translateX(100%); } } | ||
@@ -345,11 +347,30 @@ @keyframes nb-drawer-in-left { from { transform: translateX(-100%); } } | ||
| ensureStyle(STYLE_ID3, RULES3); | ||
| const dialogRef = useRef(null); | ||
| const panelRef = useRef(null); | ||
| const labelId = useId(); | ||
| const [hasTitle, setHasTitle] = useState(false); | ||
| useEffect(() => { | ||
| const dialog = dialogRef.current; | ||
| if (!dialog || typeof dialog.showModal !== "function") return; | ||
| if (open && !dialog.open) dialog.showModal(); | ||
| else if (!open && dialog.open) dialog.close(); | ||
| if (!open) return; | ||
| const onKey = (e) => { | ||
| if (e.key === "Escape") { | ||
| e.preventDefault(); | ||
| (onEscape ?? onClose)(); | ||
| } | ||
| }; | ||
| window.addEventListener("keydown", onKey); | ||
| return () => window.removeEventListener("keydown", onKey); | ||
| }, [open, onEscape, onClose]); | ||
| useEffect(() => { | ||
| if (!open) return; | ||
| const previouslyFocused = document.activeElement; | ||
| panelRef.current?.focus(); | ||
| const { body } = document; | ||
| const prevOverflow = body.style.overflow; | ||
| body.style.overflow = "hidden"; | ||
| return () => { | ||
| body.style.overflow = prevOverflow; | ||
| previouslyFocused?.focus?.(); | ||
| }; | ||
| }, [open]); | ||
| const ctxValue = useMemo(() => ({ labelId, setHasTitle }), [labelId]); | ||
| if (!open) return null; | ||
| const isBottom = side === "bottom"; | ||
@@ -361,22 +382,27 @@ const panelStyle = { | ||
| boxShadow: tokens.shadowLg, | ||
| outline: "none", | ||
| ...side === "right" ? { borderLeft: `${tokens.borderWidth} solid ${tokens.border}` } : side === "left" ? { borderRight: `${tokens.borderWidth} solid ${tokens.border}` } : { borderTop: `${tokens.borderWidth} solid ${tokens.border}` }, | ||
| ...style | ||
| }; | ||
| const ctxValue = useMemo(() => ({ labelId, setHasTitle }), [labelId]); | ||
| return /* @__PURE__ */ jsx(DrawerContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsx( | ||
| "dialog", | ||
| "div", | ||
| { | ||
| ref: dialogRef, | ||
| className: `nb-drawer nb-drawer--${side}`, | ||
| style: panelStyle, | ||
| "aria-labelledby": hasTitle ? labelId : void 0, | ||
| onCancel: (e) => { | ||
| e.preventDefault(); | ||
| (onEscape ?? onClose)(); | ||
| }, | ||
| className: `nb-drawer-scrim nb-drawer-scrim--${side}`, | ||
| onClick: (e) => { | ||
| if (e.target === dialogRef.current) onClose(); | ||
| if (e.target === e.currentTarget) onClose(); | ||
| }, | ||
| ...rest, | ||
| children | ||
| children: /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| ref: panelRef, | ||
| role: "dialog", | ||
| "aria-modal": "true", | ||
| "aria-labelledby": hasTitle ? labelId : void 0, | ||
| tabIndex: -1, | ||
| className: `nb-drawer nb-drawer--${side}`, | ||
| style: panelStyle, | ||
| ...rest, | ||
| children | ||
| } | ||
| ) | ||
| } | ||
@@ -383,0 +409,0 @@ ) }); |
+1
-1
| { | ||
| "name": "@nimblebrain/synapse", | ||
| "version": "0.12.0", | ||
| "version": "0.12.1", | ||
| "description": "Agent-aware app SDK for the MCP ext-apps protocol", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+9
-5
@@ -40,2 +40,6 @@ # @nimblebrain/synapse | ||
| **Building a Python MCP server?** The server half — one self-contained `ui://` | ||
| component rendered across ChatGPT, Claude, and NimbleBrain — ships as the | ||
| [`nimblebrain-synapse`](python/README.md) PyPI package (`pip install nimblebrain-synapse`). | ||
| ## Package Exports | ||
@@ -117,3 +121,3 @@ | ||
| ```tsx | ||
| import { AppProvider, useToolResult, useCallTool, useResize } from "@nimblebrain/synapse/react"; | ||
| import { AppProvider, useToolResult, useResize } from "@nimblebrain/synapse/react"; | ||
@@ -130,3 +134,2 @@ function App() { | ||
| const result = useToolResult(); | ||
| const { call, data, isPending } = useCallTool("list_items"); | ||
| const resize = useResize(); | ||
@@ -342,3 +345,3 @@ | ||
| | `callTool(name, args?)` | Call an MCP tool and get typed result | | ||
| | `callToolAsTask(name, args?, opts?)` | Call a long-running tool task-augmented; returns a `TaskHandle` immediately. See [Long-running tools](#long-running-tools-tasks) below. | | ||
| | `callToolAsTask(name, args?, opts?)` | Call a long-running tool task-augmented; returns a `Promise<TaskHandle>`. See [Long-running tools](#long-running-tools-tasks) below. | | ||
| | `onDataChanged(cb)` | Subscribe to data change events | | ||
@@ -366,3 +369,3 @@ | `onAction(cb)` | Subscribe to agent actions (typed, declarative) | | ||
| ```tsx | ||
| import { AppProvider, useApp, useToolResult, useToolInput, useResize, useCallTool } from "@nimblebrain/synapse/react"; | ||
| import { AppProvider, useApp, useToolResult, useToolInput, useResize } from "@nimblebrain/synapse/react"; | ||
| ``` | ||
@@ -377,4 +380,5 @@ | ||
| | `useResize()` | `(w?, h?) => void` | Resize helper — auto-measures body if no args | | ||
| | `useCallTool(name)` | `{ call, data, isPending, error }` | Call a tool with loading/error state | | ||
| To call a tool on this path, use the `App` instance: `useApp()`, then `app.callTool(name, args)`. The `useCallTool` hook (with built-in loading state) belongs to the `SynapseProvider` set below. | ||
| ### `SynapseProvider`-based (Legacy) | ||
@@ -381,0 +385,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
1947623
0.31%12625
0.41%481
0.84%