@nimblebrain/synapse
Advanced tools
+34
-1
@@ -336,2 +336,14 @@ 'use strict'; | ||
| `; | ||
| function inClosedDetails(el) { | ||
| const details = el.closest("details:not([open])"); | ||
| if (!details) return false; | ||
| const summary = details.querySelector("summary"); | ||
| return !summary || !summary.contains(el); | ||
| } | ||
| var CANDIDATES = "a[href], button, input, select, textarea, summary, [tabindex]"; | ||
| function tabbables(panel) { | ||
| return Array.from(panel.querySelectorAll(CANDIDATES)).filter( | ||
| (el) => el.tabIndex >= 0 && !el.matches(":disabled") && !el.closest("[hidden]") && !inClosedDetails(el) && el.type !== "hidden" | ||
| ); | ||
| } | ||
| var DrawerContext = react.createContext(null); | ||
@@ -365,8 +377,29 @@ function DrawerRoot({ | ||
| if (!open) return; | ||
| const panel = panelRef.current; | ||
| const previouslyFocused = document.activeElement; | ||
| panelRef.current?.focus(); | ||
| panel?.focus(); | ||
| const { body } = document; | ||
| const prevOverflow = body.style.overflow; | ||
| body.style.overflow = "hidden"; | ||
| const onKeyDown = (e) => { | ||
| if (e.key !== "Tab" || !panel) return; | ||
| const focusables = tabbables(panel); | ||
| if (focusables.length === 0) { | ||
| e.preventDefault(); | ||
| return; | ||
| } | ||
| const first = focusables[0]; | ||
| const last = focusables[focusables.length - 1]; | ||
| const active = document.activeElement; | ||
| if (e.shiftKey && (active === first || active === panel)) { | ||
| e.preventDefault(); | ||
| last.focus(); | ||
| } else if (!e.shiftKey && active === last) { | ||
| e.preventDefault(); | ||
| first.focus(); | ||
| } | ||
| }; | ||
| panel?.addEventListener("keydown", onKeyDown); | ||
| return () => { | ||
| panel?.removeEventListener("keydown", onKeyDown); | ||
| body.style.overflow = prevOverflow; | ||
@@ -373,0 +406,0 @@ previouslyFocused?.focus?.(); |
+34
-1
@@ -334,2 +334,14 @@ import { ensureStyle, injectBaseReset } from '../chunk-3HQGHPNC.js'; | ||
| `; | ||
| function inClosedDetails(el) { | ||
| const details = el.closest("details:not([open])"); | ||
| if (!details) return false; | ||
| const summary = details.querySelector("summary"); | ||
| return !summary || !summary.contains(el); | ||
| } | ||
| var CANDIDATES = "a[href], button, input, select, textarea, summary, [tabindex]"; | ||
| function tabbables(panel) { | ||
| return Array.from(panel.querySelectorAll(CANDIDATES)).filter( | ||
| (el) => el.tabIndex >= 0 && !el.matches(":disabled") && !el.closest("[hidden]") && !inClosedDetails(el) && el.type !== "hidden" | ||
| ); | ||
| } | ||
| var DrawerContext = createContext(null); | ||
@@ -363,8 +375,29 @@ function DrawerRoot({ | ||
| if (!open) return; | ||
| const panel = panelRef.current; | ||
| const previouslyFocused = document.activeElement; | ||
| panelRef.current?.focus(); | ||
| panel?.focus(); | ||
| const { body } = document; | ||
| const prevOverflow = body.style.overflow; | ||
| body.style.overflow = "hidden"; | ||
| const onKeyDown = (e) => { | ||
| if (e.key !== "Tab" || !panel) return; | ||
| const focusables = tabbables(panel); | ||
| if (focusables.length === 0) { | ||
| e.preventDefault(); | ||
| return; | ||
| } | ||
| const first = focusables[0]; | ||
| const last = focusables[focusables.length - 1]; | ||
| const active = document.activeElement; | ||
| if (e.shiftKey && (active === first || active === panel)) { | ||
| e.preventDefault(); | ||
| last.focus(); | ||
| } else if (!e.shiftKey && active === last) { | ||
| e.preventDefault(); | ||
| first.focus(); | ||
| } | ||
| }; | ||
| panel?.addEventListener("keydown", onKeyDown); | ||
| return () => { | ||
| panel?.removeEventListener("keydown", onKeyDown); | ||
| body.style.overflow = prevOverflow; | ||
@@ -371,0 +404,0 @@ previouslyFocused?.focus?.(); |
+1
-1
| { | ||
| "name": "@nimblebrain/synapse", | ||
| "version": "0.12.1", | ||
| "version": "0.12.2", | ||
| "description": "Agent-aware app SDK for the MCP ext-apps protocol", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+4
-2
@@ -85,4 +85,6 @@ # @nimblebrain/synapse | ||
| fullscreen, split, or a narrow rail regardless of screen size. | ||
| - **Lean on the platform.** `Drawer` is built on the native `<dialog>` element | ||
| (focus-trap, Escape, scroll behavior for free) rather than re-implementing them. | ||
| - **Sandbox-safe overlays.** `Drawer` is a plain `<div>` overlay, not a native | ||
| `<dialog>`: the app iframe withholds `allow-modals`, so `<dialog>.showModal()` | ||
| throws there. The scrim, Tab focus trap, focus-in/restore, scroll-lock, and | ||
| Escape are hand-rolled. | ||
@@ -89,0 +91,0 @@ ## Quick Start |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
1960199
0.65%12691
0.52%483
0.42%