Nav — JavaScript
import { Nav } from "@popovandrii/ui-elements";
import "@popovandrii/ui-elements/style.css";
const nav = new Nav();One call binds every .UInav on the page. The markup is on the HTML & CSS page.
Constructor
new Nav(selectors?, debug?, options?);| Option | Default | |
|---|---|---|
root | document | limit scanning to a subtree |
observe | false | debounced MutationObserver re-scan |
closeOnNavigate | true | close the drawer when a link inside the panel is clicked |
Leave closeOnNavigate on unless you have a reason: without it a hash or SPA navigation leaves the drawer sitting on top of the view it just navigated to.
new Nav({}, false, { root: document.querySelector("#app")!, observe: true });Methods
| Method | |
|---|---|
open(el) / close(el) / toggle(el) | drive the drawer |
isOpen(el) | boolean |
scan() | bind newly added .UInavs — safe to call repeatedly |
destroy() | release scan-scoped listeners; the manager stays reusable |
dispose() | destroy() plus the lifetime-scoped Escape / Tab / resize listeners |
Only one drawer is open at a time — opening a second closes the first.
const bar = document.querySelector<HTMLElement>("#site-nav")!;
nav.open(bar);
nav.isOpen(bar); // true
nav.close(bar);Events
Both bubble and carry the nav's id:
document.addEventListener("ui-nav-open", (e) => {
console.log((e as CustomEvent).detail.id);
});
document.addEventListener("ui-nav-close", () => {
/* … */
});Auto-hide
Add .UInav--auto-hide to a sticky bar and the manager takes it from there — it watches the page scroll and toggles .UInav--hidden. The markup side is on the HTML & CSS page; what the manager guarantees:
- the scroll listener is passive and does nothing but queue a frame —
scrollYis read insiderequestAnimationFrame, off the scroll thread's critical path; - a 6px threshold absorbs momentum and rubber-band, so the bar doesn't twitch;
- an open drawer pins the bar in place, and
open()brings a hidden bar back before it measures it — the drawer hangs off the bar's real bottom edge.
Both class names are in the selectors map, so you can rename them like any other:
new Nav({ autoHide: "site-header--auto-hide", hidden: "site-header--hidden" });Singleton factory
import { getNavManager, resetManagers } from "@popovandrii/ui-elements";
getNavManager(); // one shared manager for the page
resetManagers(); // tear every manager down (Nav gets the full dispose())What the manager does to the page while the drawer is open
The drawer is modal, so opening it isn't a local change:
- everything outside the nav gets
inert— pointer,Taband screen reader alike; document.bodystops scrolling, and the scrollbar's gutter is paid back as padding so the layout doesn't jump sideways;Escapecloses from anywhere; focus lands in the drawer on open and returns to the toggle on close;Tabis trapped across the whole bar — the close button and the pinned controls sit outside the panel, and trapping them out of reach would strand the user in the drawer.
All of it is undone on close, and on destroy() even if the drawer was open.
Two things are deliberately left alone:
- Toasts are never inerted — they outrank the drawer (
z-index: 9999) and can arrive while it's open, so inerting them would render a notification nobody can dismiss. - An
inertyou set yourself stays set; the manager only removes its own.
SPA lifecycle
const nav = new Nav({}, false, { root: view, observe: true });
// on unmount
nav.dispose();destroy() un-freezes the page and drops the backdrop it created, then leaves the manager ready for another scan(). Use dispose() when the manager itself is going away.