@nimblebrain/synapse 0.12.1 → 0.12.2
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/README.md +4 -2
- package/dist/ui/index.cjs +34 -1
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.js +34 -1
- package/dist/ui/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,8 +82,10 @@ These are the durable decisions behind the library; they rarely change.
|
|
|
82
82
|
- **Responsive to the pane, not the device.** Layouts observe their own width
|
|
83
83
|
(`ResizeObserver` via `useBreakpoint`), because an app's iframe may be
|
|
84
84
|
fullscreen, split, or a narrow rail regardless of screen size.
|
|
85
|
-
- **
|
|
86
|
-
|
|
85
|
+
- **Sandbox-safe overlays.** `Drawer` is a plain `<div>` overlay, not a native
|
|
86
|
+
`<dialog>`: the app iframe withholds `allow-modals`, so `<dialog>.showModal()`
|
|
87
|
+
throws there. The scrim, Tab focus trap, focus-in/restore, scroll-lock, and
|
|
88
|
+
Escape are hand-rolled.
|
|
87
89
|
|
|
88
90
|
## Quick Start
|
|
89
91
|
|
package/dist/ui/index.cjs
CHANGED
|
@@ -333,6 +333,18 @@ var RULES3 = `
|
|
|
333
333
|
.nb-drawer-iconbtn { min-width: 44px; min-height: 44px; }
|
|
334
334
|
}
|
|
335
335
|
`;
|
|
336
|
+
function inClosedDetails(el) {
|
|
337
|
+
const details = el.closest("details:not([open])");
|
|
338
|
+
if (!details) return false;
|
|
339
|
+
const summary = details.querySelector("summary");
|
|
340
|
+
return !summary || !summary.contains(el);
|
|
341
|
+
}
|
|
342
|
+
var CANDIDATES = "a[href], button, input, select, textarea, summary, [tabindex]";
|
|
343
|
+
function tabbables(panel) {
|
|
344
|
+
return Array.from(panel.querySelectorAll(CANDIDATES)).filter(
|
|
345
|
+
(el) => el.tabIndex >= 0 && !el.matches(":disabled") && !el.closest("[hidden]") && !inClosedDetails(el) && el.type !== "hidden"
|
|
346
|
+
);
|
|
347
|
+
}
|
|
336
348
|
var DrawerContext = react.createContext(null);
|
|
337
349
|
function DrawerRoot({
|
|
338
350
|
open,
|
|
@@ -361,12 +373,33 @@ function DrawerRoot({
|
|
|
361
373
|
}, [open, onEscape, onClose]);
|
|
362
374
|
react.useEffect(() => {
|
|
363
375
|
if (!open) return;
|
|
376
|
+
const panel = panelRef.current;
|
|
364
377
|
const previouslyFocused = document.activeElement;
|
|
365
|
-
|
|
378
|
+
panel?.focus();
|
|
366
379
|
const { body } = document;
|
|
367
380
|
const prevOverflow = body.style.overflow;
|
|
368
381
|
body.style.overflow = "hidden";
|
|
382
|
+
const onKeyDown = (e) => {
|
|
383
|
+
if (e.key !== "Tab" || !panel) return;
|
|
384
|
+
const focusables = tabbables(panel);
|
|
385
|
+
if (focusables.length === 0) {
|
|
386
|
+
e.preventDefault();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const first = focusables[0];
|
|
390
|
+
const last = focusables[focusables.length - 1];
|
|
391
|
+
const active = document.activeElement;
|
|
392
|
+
if (e.shiftKey && (active === first || active === panel)) {
|
|
393
|
+
e.preventDefault();
|
|
394
|
+
last.focus();
|
|
395
|
+
} else if (!e.shiftKey && active === last) {
|
|
396
|
+
e.preventDefault();
|
|
397
|
+
first.focus();
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
panel?.addEventListener("keydown", onKeyDown);
|
|
369
401
|
return () => {
|
|
402
|
+
panel?.removeEventListener("keydown", onKeyDown);
|
|
370
403
|
body.style.overflow = prevOverflow;
|
|
371
404
|
previouslyFocused?.focus?.();
|
|
372
405
|
};
|